summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/embedded-content/the-img-element
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/embedded-content/the-img-element')
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpgbin0 -> 91072 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html42
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html91
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html17
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html69
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html37
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html54
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html82
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html121
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html133
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html128
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html138
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html18
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html65
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpgbin0 -> 389245 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html70
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html10
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html31
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html47
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html51
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html63
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html23
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html65
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html58
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html57
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html40
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html60
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html61
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html46
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html83
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html55
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html62
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html112
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.pngbin0 -> 268 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html16
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html24
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html200
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html68
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html36
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html45
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html52
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html21
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html63
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html49
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html30
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html64
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html78
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html628
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.pngbin0 -> 76 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpgbin0 -> 21474 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.pngbin0 -> 91 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py44
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html22
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.pngbin0 -> 11493 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.pngbin0 -> 510 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py14
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers1
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html12
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html19
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html7
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html8
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html26
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html153
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html33
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js29
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html186
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html50
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html245
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.pngbin0 -> 268 bytes
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers3
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html20
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css2
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js15
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html4
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html6
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html32
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html38
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html25
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html140
-rw-r--r--testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html93
166 files changed, 6822 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpg
new file mode 100644
index 0000000000..d30ac2ac36
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/3.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html
new file mode 100644
index 0000000000..73b937f67f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>404 response with actual image data should be rendered and load event is fired</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img id="img">
+
+<script>
+ async_test(t => {
+ var img = document.getElementById("img");
+ img.onload = t.step_func_done(e => {
+ assert_equals(e.type, "load", "image.onload() called");
+ });
+ img.onerror = t.unreached_func("image.onerror() was not supposed to be called");
+ img.src = "404-response-with-actual-image-data.py";
+ }, "404 response with actual image data should be rendered and load event is fired");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py
new file mode 100644
index 0000000000..083aa90b41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/404-response-with-actual-image-data.py
@@ -0,0 +1,4 @@
+from base64 import decodebytes
+
+def main(req, res):
+ return 404, [(b'Content-Type', b'image/png')], decodebytes(b"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAhSURBVDhPY3wro/KfgQLABKXJBqMGjBoAAqMGDLwBDAwAEsoCTFWunmQAAAAASUVORK5CYII=")
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html
new file mode 100644
index 0000000000..3cfef5f4f9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/Image-constructor.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<title>DOM Image constructor Test</title>
+<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<meta name="assert" content="Tests the Image constructor for the img-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+
+<div id="log"></div>
+<script>
+ test(function() {
+ var img = new Image();
+ assert_true(img != undefined);
+ }, "Image constructor works");
+
+ test(function() {
+ assert_equals(Image.prototype, HTMLImageElement.prototype);
+ }, "Image and HTMLImageElement share a prototype");
+
+ test(function() {
+ assert_true((new Image()).localName === "img");
+ }, "Image localName is img");
+
+ test(function() {
+ assert_true((new Image()).namespaceURI === "http://www.w3.org/1999/xhtml");
+ }, "Image namespace URI is correct");
+
+ test(function() {
+ assert_equals(Image.name, "Image", "Image name should be Image (not HTMLImageElement)");
+ assert_equals(Object.getPrototypeOf(Image), Function.prototype, "Image's prototype is Function.prototype");
+ assert_equals(Image.prototype, HTMLImageElement.prototype, "Image.prototype is same as HTMLImageElement.prototype");
+ assert_equals(Object.getPrototypeOf(new Image()), HTMLImageElement.prototype, "new Image()'s prototype is HTMLImageElement.prototype ");
+ assert_equals(Object.getPrototypeOf(Image.prototype), HTMLElement.prototype, "Image.prototype's prototype is HTMLElement.prototype");
+
+ const desc = Object.getOwnPropertyDescriptor(Image, "prototype");
+ assert_false(desc.configurable, "Image.prototype is not configurable");
+ assert_false(desc.enumerable, "Image.prototype is not enumerable");
+ assert_false(desc.writable, "Image.prototype is not writable");
+ }, "NamedConstructor creates the correct object structure.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html
new file mode 100644
index 0000000000..30729fc81c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adopt-from-image-document.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>Adopt img from image document</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<link rel="match" href="document-base-url-ref.html">
+<!-- Counteract any style added by the image document -->
+<style>img { width: initial; height: initial; }</style>
+<iframe></iframe>
+<script>
+ var iframe = document.querySelector('iframe');
+ iframe.onload = function() {
+ let img = iframe.contentDocument.body.firstChild;
+ document.body.appendChild(img);
+ iframe.remove();
+ };
+ iframe.src = 'resources/cat.jpg';
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html
new file mode 100644
index 0000000000..15e02bcf51
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/adoption.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Adopting an image updates the image data</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+
+<!-- tests -->
+
+<div id="adoptTest1"></div>
+<picture id="adoptTest2">
+<source srcset="/images/green-2x2.png">
+</picture>
+
+<script>
+function resolve(url) {
+ if (url === "") {
+ return url;
+ }
+ var a = document.createElement('a');
+ a.href = url;
+ return a.href;
+}
+
+function t(desc, data, expect) {
+ async_test(function(t) {
+ var d = (new DOMParser()).parseFromString(data, 'text/html');
+ var i = d.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.step_func_done(function() {
+ assert_equals(i.currentSrc, resolve(expect));
+ });
+ var n = d.querySelector('[adopt-node]');
+ document.adoptNode(n);
+ }, desc);
+}
+
+onload = function() {
+
+ t('img (src only)',
+ '<img src="/images/green-1x1.png" adopt-node>',
+ '/images/green-1x1.png');
+
+ t('img (src only), parent is picture',
+ '<picture adopt-node><img src="/images/green-1x1.png"></picture>',
+ '/images/green-1x1.png');
+
+ t('img (src only), previous sibling is source',
+ '<picture adopt-node><source srcset="/images/green-1x1.png"><img src="/images/green-2x2.png"></picture>',
+ '/images/green-1x1.png');
+
+ t('img (srcset 1 cand)',
+ '<img srcset="/images/green-1x1.png" adopt-node>',
+ '/images/green-1x1.png');
+
+ t('img (srcset 1 cand), parent is picture',
+ '<picture adopt-node><img srcset="/images/green-1x1.png"></picture>',
+ '/images/green-1x1.png');
+
+ t('img (srcset 1 cand), previous sibling is source',
+ '<picture adopt-node><source srcset="/images/green-1x1.png"><img srcset="/images/green-2x2.png"></picture>',
+ '/images/green-1x1.png');
+
+ async_test(function(t) {
+ var d = (new DOMParser()).parseFromString('<template><img src="/images/green-1x1.png"></template>', 'text/html');
+ var i = d.querySelector('template').content.querySelector('img').cloneNode(1);
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.step_func_done(function() {
+ assert_equals(i.currentSrc, resolve('/images/green-1x1.png'));
+ });
+
+ document.getElementById('adoptTest1').appendChild(i);
+ }, 'adopt a cloned img in template');
+
+ async_test(function(t) {
+ var preload = new Image();
+ preload.src = '/images/green-1x1.png?' + Math.random();
+ preload.onload = t.step_func(function() {
+ var d = (new DOMParser()).parseFromString('<img src="' + preload.src + '">', 'text/html');
+ var i = d.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.step_func_done(function() {
+ assert_equals(i.currentSrc, resolve("/images/green-2x2.png"));
+ });
+
+ var p = document.getElementById('adoptTest2');
+ p.appendChild(i);
+ });
+ }, 'adoption is from appendChild');
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html
new file mode 100644
index 0000000000..4a63bd7a7a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/already-loaded-image-sync-width.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Image dimensions are available synchronously after changing src to an already-loaded image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1797798">
+<img id="existing">
+<script>
+ let src = "/images/green.png";
+ let existing = document.getElementById("existing");
+ async_test(function(t) {
+ let tmp = document.createElement("img");
+ tmp.src = src;
+ tmp.onload = t.step_func_done(function() {
+ existing.src = src;
+ assert_equals(existing.width, 100);
+ assert_equals(existing.height, 50);
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html
new file mode 100644
index 0000000000..5fc5cb8b61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-onload.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<title>Ensure images from available images list can be drawn to a canvas</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-list-of-available-images">
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ async_test(function(t) {
+ var i = new Image();
+ i.onerror = t.unreached_func();
+ i.onload = t.step_func(function() {
+ var i2 = new Image();
+ // Potentially start multiple image loading tasks by performing several
+ // relevant mutations. This could lead to an invalid state later in an
+ // erroneous implementation.
+ i2.crossOrigin = true;
+ // Start an image loading task that is expected to short-circuit since
+ // the requested image is present in the list of available images.
+ i2.src = "3.jpg";
+ i2.onerror = t.unreached_func();
+ // Ensure the loaded image is in a state that is usable by a 2d canvas.
+ i2.onload = t.step_func_done(function() {
+ var c = document.createElement('canvas');
+ var ctx = c.getContext('2d');
+ ctx.drawImage(i2, 0, 0);
+ });
+ });
+ // Request an image which should be added to the list of available images.
+ i.src = "3.jpg";
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html
new file mode 100644
index 0000000000..8061abae50
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<img src="3.jpg">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html
new file mode 100644
index 0000000000..779ff97868
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/available-images.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Ensure images from available images list are rendered</title>
+<meta charset="utf-8">
+<link rel="match" href="available-images-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<div id="log"></div>
+<script>
+ var i = new Image();
+ i.onload = function() {
+ var i2 = new Image();
+ i2.src = "3.jpg";
+ document.body.appendChild(i2);
+ document.documentElement.classList.remove("reftest-wait");
+ };
+ i.src = "3.jpg";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html
new file mode 100644
index 0000000000..b1dee3a3ca
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/below-viewport-image-loading-lazy-load-event.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport loading=lazy images do not block the window load event
+ when scrolled into viewport</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- When this image loads, we will scroll the below-viewport loading=lazy
+ images into the viewport. This happens before the window load event is
+ fired -->
+ <img id="scroll_trigger"
+ src="resources/image.png?scroll-trigger&pipe=trickle(d1)"
+ onload="scroll_trigger_img.resolve();" onerror="scroll_trigger_img.reject();">
+ <!-- This image blocks the window load event for 2 seconds -->
+ <img src="resources/image.png?window-load-blocking&pipe=trickle(d2)">
+
+ <div style="height:1000vh"></div>
+ <!-- These images must load because they intersect the viewport, but they must
+ not block the window load event, because they are loading=lazy -->
+ <img id="visible"
+ src="resources/image.png?visible&pipe=trickle(d3)" loading="lazy"
+ onload="visible_img.resolve();" onerror="visible_img.reject();">
+ <img id="visibility_hidden" style="visibility:hidden;"
+ src="resources/image.png?visibility_hidden&pipe=trickle(d3)" loading="lazy"
+ onload="visibility_hidden_img.resolve();" onerror="visibility_hidden_img.reject();">
+</body>
+
+<script>
+ const scroll_trigger_img = new ElementLoadPromise("visible");
+ const visible_img = new ElementLoadPromise("visible");
+ const visibility_hidden_img = new ElementLoadPromise("visibility_hidden");
+
+ async_test(t => {
+ let has_window_loaded = false;
+
+ scroll_trigger_img.promise
+ .then(t.step_func(() => {
+ assert_false(has_window_loaded,
+ "The scroll_trigger image should load before the window " +
+ "load event fires");
+ visibility_hidden_img.element().scrollIntoView();
+ }))
+ .catch(t.unreached_func("The scroll_trigger image should load"));
+
+ window.addEventListener("load", t.step_func(() => {
+ has_window_loaded = true;
+ }));
+
+ Promise.all([visible_img.promise, visibility_hidden_img.promise])
+ .then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "The window load event should fire before the " +
+ "below-viewport loading=lazy images load");
+ assert_true(visible_img.element().complete,
+ "The below-viewport loading=lazy visible image is complete");
+ assert_true(visibility_hidden_img.element().complete,
+ "The below-viewport loading=lazy visibility:hidden image is complete");
+ }))
+ .catch(t.unreached_func("The images should load successfully"));
+
+ }, "Below-viewport loading=lazy images do not block the window load event when " +
+ "scrolled into viewport");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg
new file mode 100644
index 0000000000..ccff177ae9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/brokenimg.jpg
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<html>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html
new file mode 100644
index 0000000000..f7d47b3640
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/basic.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>img current pixel density basic</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<img src="/images/green-256x256.png" data-expect="256">
+<img srcset="/images/green-256x256.png 1x" data-expect="256">
+<img srcset="/images/green-256x256.png 1.6x" data-expect="160">
+<img srcset="/images/green-256x256.png 2x" data-expect="128">
+<img srcset="/images/green-256x256.png 10000x" data-expect="0">
+<img srcset="/images/green-256x256.png 9e99999999999999999999999x" data-expect="0">
+<img srcset="/images/green-256x256.png 256w" sizes="256px" data-expect="256">
+<img srcset="/images/green-256x256.png 512w" sizes="256px" data-expect="128">
+<img srcset="/images/green-256x256.png 256w" sizes="512px" data-expect="512">
+<img srcset="/images/green-256x256.png 256w" sizes="1px" data-expect="1">
+<img srcset="/images/green-256x256.png 256w" sizes="0px" data-expect="0">
+<!-- SVG -->
+<img srcset="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='-1%20-1%202%202'%20width='20'%20height='20'><circle%20r='1'/></svg> 2x" data-expect="10">
+<img srcset="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='-1%20-1%202%202'%20width='20'><circle%20r='1'/></svg> 2x" data-expect="10">
+<img srcset="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='-1%20-1%202%202'%20height='20'><circle%20r='1'/></svg> 2x" data-expect="10">
+<script>
+setup({explicit_done:true});
+onload = function() {
+ [].forEach.call(document.images, function(img) {
+ test(function() {
+ var expected = parseFloat(img.dataset.expect);
+ assert_equals(img.width, expected, 'width');
+ assert_equals(img.height, expected, 'height');
+ assert_equals(img.clientWidth, expected, 'clientWidth');
+ assert_equals(img.clientHeight, expected, 'clientHeight');
+ assert_equals(img.naturalWidth, expected, 'naturalWidth');
+ assert_equals(img.naturalHeight, expected, 'naturalHeight');
+ }, img.outerHTML);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html
new file mode 100644
index 0000000000..5e328b5e2d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/current-pixel-density/error.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<title>img current pixel density error</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<img id=ref src="404" alt="testing">
+<img srcset="404" alt="testing">
+<img srcset="404 0.5x" alt="testing">
+<img srcset="404 2x" alt="testing">
+<img srcset="404 100w" alt="testing">
+<img srcset="404 100w" sizes="500px" alt="testing">
+<picture><img src="404 100w" sizes="500px" alt="testing"></picture>
+<script>
+setup({explicit_done:true});
+onload = function() {
+ var ref = document.getElementById("ref");
+ var expected_width = ref.width;
+ var expected_height = ref.height;
+ [].forEach.call(document.images, function(img) {
+ test(function() {
+ assert_not_equals(expected_width, 0, 'expected_width');
+ assert_not_equals(expected_height, 0, 'expected_height');
+ assert_equals(img.width, expected_width, 'width');
+ assert_equals(img.height, expected_height, 'height');
+ assert_equals(img.naturalWidth, 0, 'naturalWidth');
+ assert_equals(img.naturalHeight, 0, 'naturalHeight');
+ }, img.outerHTML);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html
new file mode 100644
index 0000000000..a5e108dcd6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/currentSrc-blob-cache.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>currentSrc is right even if underlying image is a shared blob</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1625786">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<img id="first">
+<img id="second">
+<script>
+promise_test(async t => {
+ let canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+ let ctx = canvas.getContext("2d");
+ ctx.fillStyle = "green";
+ ctx.rect(0, 0, 100, 100);
+ ctx.fill();
+
+ let blob = await new Promise(resolve => canvas.toBlob(resolve));
+
+ let first = document.querySelector("#first");
+ let second = document.querySelector("#second");
+
+ let firstLoad = new Promise(resolve => {
+ first.addEventListener("load", resolve, { once: true });
+ });
+
+ let secondLoad = new Promise(resolve => {
+ second.addEventListener("load", resolve, { once: true });
+ });
+
+ let uri1 = URL.createObjectURL(blob);
+ let uri2 = URL.createObjectURL(blob);
+ first.src = uri1;
+ second.src = uri2;
+
+ await firstLoad;
+ await secondLoad;
+
+ assert_equals(first.src, first.currentSrc);
+ assert_equals(second.src, second.currentSrc);
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html
new file mode 100644
index 0000000000..808b5c884c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/data-url.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>data URL image</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+setup({ single_test: true });
+
+var c = document.createElement("canvas"),
+ con = c.getContext("2d"),
+ img = document.createElement("img")
+img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAA+UlEQVR4nO3RoRHAQBDEsOu/6YR+B2sgIO4Z3919pMwDMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHoAhafMADEmbB2BI2jwAQ9LmARiSNg/AkLR5AIakzQMwJG0egCFp8wAMSZsHYEjaPABD0uYBGJI2D8CQtHkAhqTNAzAkbR6AIWnzAAxJmwdgSNo8AEPS5gEYkjYPwJC0eQCGpM0DMCRtHsDjB5K06yueJFXJAAAAAElFTkSuQmCC"
+img.onload = () => {
+ con.drawImage(img, 0, 0)
+ var data = con.getImageData(0, 0, 10, 10) // should not throw as data URLs are same-origin
+ for(var i = 0; i < data.data.length; i++) {
+ var expected = ((i+1) % 4 == 0) ? 255 : 0
+ assert_equals(data.data[i], expected)
+ }
+ c.toDataURL() // shouldn't throw either
+ done()
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html
new file mode 100644
index 0000000000..ed14a007a6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-iframe.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), iframe tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="frame_loaded" srcdoc="iframe"></iframe>
+<iframe id="frame_notloaded" srcdoc="iframe"></iframe>
+<iframe id="frame_notloaded2" srcdoc="iframe"></iframe>
+
+<script>
+"use strict";
+
+promise_test(function() {
+ return new Promise(function(resolve, reject) {
+ var frame = document.getElementById("frame_loaded");
+ var img = frame.contentDocument.createElement("img");
+ img.src = "/images/green.png";
+ img.onload = function() {
+ // At this point the frame which created the img is removed, so decode() should fail.
+ frame.parentNode.removeChild(frame);
+ img.decode().then(function() {
+ assert_unreached("Unexpected success");
+ }, function() {
+ resolve();
+ });
+ };
+ });
+}, document.title + " Decode from removed iframe fails (loaded img)");
+
+promise_test(function(t) {
+ var frame = document.getElementById("frame_notloaded");
+ var img = frame.contentDocument.createElement("img");
+ img.src = "/images/green.png";
+ frame.parentNode.removeChild(frame);
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Decode from removed iframe fails (img not loaded)");
+
+promise_test(function(t) {
+ var frame = document.getElementById("frame_notloaded2");
+ var img = frame.contentDocument.createElement("img");
+ img.src = "/images/green.png";
+ // First request a promise, then remove the iframe.
+ var promise = img.decode();
+ frame.parentNode.removeChild(frame);
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Decode from iframe, later removed, fails (img not loaded)");
+
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html
new file mode 100644
index 0000000000..e54ae223a0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-image-document.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>HTMLImageElement.prototype.decode(), image document tests.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="frame_imgdoc" src="about:blank"></iframe>
+<script>
+"use strict";
+
+promise_test(function() {
+ return new Promise(function(resolve) {
+ var frame = document.getElementById("frame_imgdoc");
+ // Load an image in the iframe and then replace that.
+ frame.src = "/images/red.png";
+ frame.onload = function() {
+ let img = frame.contentDocument.body.firstElementChild;
+ img.src = "/images/green.png";
+ img.decode().then(function() {
+ resolve();
+ });
+ };
+ });
+}, document.title + " Decode from iframe with image document, succeeds (img not loaded)");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html
new file mode 100644
index 0000000000..1bc53a1f18
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes-svg.tentative.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>SVGImageElement.prototype.decode(), href mutation tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var promise = img.decode();
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.svg");
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " xlink:href changes fail decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var promise = img.decode();
+ img.setAttribute('href', "/images/green.svg");
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " href changes fail decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.svg");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " xlink:href changes fail decode; following good decode succeeds.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttribute('href', "/images/green.svg");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " href changes fail decode; following good decode succeeds.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/non/existent/path.png");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " xlink:href changes fail decode; following bad decode fails.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var first_promise = img.decode();
+ img.setAttribute('href', "/non/existent/path.png");
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " href changes fail decode; following bad decode fails.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html
new file mode 100644
index 0000000000..4b878c1bae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-path-changes.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), src/srcset mutation tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var promise = img.decode();
+ img.src = "/images/green.svg";
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " src changes fail decode.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ img.src = "/images/blue.png";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " src changes fail decode; following good png decode succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ img.src = "/images/green.svg";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " src changes fail decode; following good svg decode succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ img.src = "/non/existent/path.png";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " src changes fail decode; following bad decode fails.");
+
+promise_test(function(t) {
+ return new Promise(function(resolve, reject) {
+ var img = new Image();
+ // We wait for an onload, since the "Updating the image data" spec states
+ // that if a new microtask is scheduled, the old one is canceled so
+ // without the onload, the first decode request would be requested when the
+ // img.src is empty. With an onload, we ensure that the img.src is set and
+ // the image exists before issuing the first decode, then we verify that the
+ // src change to the same value does not prevent that request from
+ // succeeding.
+ img.onload = t.step_func(function() {
+ img.onload = null;
+
+ var first_promise = img.decode();
+ img.src = "/images/green.png";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ resolve(Promise.all([first_promise, second_promise]));
+ });
+ img.src = "/images/green.png";
+ });
+}, document.title + " src changes to the same path succeed.");
+
+// srcset tests
+// -------------------
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var promise = img.decode();
+ img.srcset = "/images/green.svg 100w";
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " srcset changes fail decode.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var first_promise = img.decode();
+ img.srcset = "/images/green.svg 100w";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ second_promise
+ ]);
+}, document.title + " srcset changes fail decode; following good decode succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var first_promise = img.decode();
+ img.srcset = "/non/existent/path.png 100w";
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ promise_rejects_dom(t, "EncodingError", first_promise),
+ promise_rejects_dom(t, "EncodingError", second_promise)
+ ]);
+}, document.title + " srcset changes fail decode; following bad decode fails.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html
new file mode 100644
index 0000000000..2f4d5e7c41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-picture.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), picture tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<picture>
+<source srcset="/images/green.png">
+<source srcset="/images/blue.png">
+<img id="testimg">
+</picture>
+
+<script>
+"use strict";
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/images/green.png";
+
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG source decodes with undefined.");
+
+promise_test(function() {
+ var img = document.getElementById("testimg");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with multiple sources decodes with undefined.");
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIA" +
+ "AAD91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsy" +
+ "AgywAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAW" +
+ "SURBVAjXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC";
+
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL source decodes with undefined.");
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/images/green.svg";
+
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG source decodes with undefined.");
+
+promise_test(function(t) {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/non/existent/path.png";
+
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent source fails decode.");
+
+promise_test(function(t) {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "data:image/png;base64,iVBO00PDR0BADBEEF00KGg";
+
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in src fails decode.");
+
+promise_test(function(t) {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Image without srcset fails decode.");
+
+promise_test(function() {
+ var picture = document.createElement("picture");
+ var source = document.createElement("source");
+ var img = document.createElement("img");
+
+ picture.appendChild(source);
+ picture.appendChild(img);
+
+ source.srcset = "/images/green.png";
+
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes for images with src succeed.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html
new file mode 100644
index 0000000000..047470f1e3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-svg.tentative.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>SVGImageElement.prototype.decode(), basic tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG xlink:href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAA" +
+ "D91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsyAgyw" +
+ "AAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAWSURBVA" +
+ "jXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL xlink:href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href',
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAA" +
+ "D91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsyAgyw" +
+ "AAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAWSURBVA" +
+ "jXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.svg");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG xlink:href decodes with undefined.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.svg");
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG href decodes with undefined.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/non/existent/path.png");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent xlink:href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/non/existent/path.png");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "data:image/png;base64,iVBO00PDR0BADBEEF00KGg");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in xlink:href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "data:image/png;base64,iVBO00PDR0BADBEEF00KGg");
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in href fails decode.");
+
+promise_test(function(t) {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Image without xlink:href or href fails decode.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes with a xlink:href succeed.");
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttribute('href', "/images/green.png");
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes with a href succeed.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html
new file mode 100644
index 0000000000..0fc49e6036
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach-svg.tentative.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>SVGImageElement.prototype.decode(), attach to DOM before promise resolves.</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<svg></svg>
+<script>
+"use strict";
+
+promise_test(function() {
+ var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "/images/green.png");
+ const promise = img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+ // Don't wait for the promise to resolve before attaching the image.
+ // The promise should still resolve successfully.
+ document.querySelector('svg').appendChild(img);
+ return promise;
+}, document.title);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html
new file mode 100644
index 0000000000..be680da619
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode-with-quick-attach.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), attach to DOM before promise resolves.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body></body>
+
+<script>
+"use strict";
+
+promise_test(function() {
+ const img = new Image();
+ img.src = "/images/green.png";
+ const promise = img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+ // Don't wait for the promise to resolve before attaching the image.
+ // The promise should still resolve successfully.
+ document.body.appendChild(img);
+ return promise;
+}, document.title);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html
new file mode 100644
index 0000000000..fac61a1446
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/decode/image-decode.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>HTMLImageElement.prototype.decode(), basic tests.</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+// src tests
+// -------------------
+promise_test(function() {
+ var img = new Image();
+ img.src = "/images/green.png";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG src decodes with undefined.");
+
+promise_test(function() {
+ var img = new Image();
+ img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAA" +
+ "D91JpzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QUSEioKsyAgyw" +
+ "AAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAWSURBVA" +
+ "jXY9y3bx8DAwPL58+fGRgYACktBRltLfebAAAAAElFTkSuQmCC";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG data URL src decodes with undefined.");
+
+promise_test(function() {
+ var img = new Image();
+ img.src = "/images/green.svg";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG src decodes with undefined.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "/non/existent/path.png";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent src fails decode.");
+
+promise_test(function(t) {
+ var inactive_doc = document.implementation.createHTMLDocument();
+ var img = inactive_doc.createElement("img");
+ img.src = "/images/green.png";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Inactive document fails decode.");
+
+promise_test(function(t) {
+ var inactive_doc = document.implementation.createHTMLDocument();
+ var img = document.createElement("img");
+ img.src = "/images/green.png";
+ var promise = img.decode();
+ inactive_doc.body.appendChild(img);
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Adopted active image into inactive document fails decode.");
+
+promise_test(function() {
+ var inactive_doc = document.implementation.createHTMLDocument();
+ var img = inactive_doc.createElement("img");
+ img.src = "/images/green.png";
+ document.body.appendChild(img);
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Adopted inactive image into active document succeeds.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.src = "data:image/png;base64,iVBO00PDR0BADBEEF00KGg";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Corrupt image in src fails decode.");
+
+promise_test(function(t) {
+ var img = new Image();
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Image without src/srcset fails decode.");
+
+promise_test(function() {
+ var img = new Image();
+ img.src = "/images/green.png";
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes for images with src succeed.");
+
+// srcset tests
+// -------------------
+promise_test(function() {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with PNG srcset decodes with undefined.");
+
+promise_test(function() {
+ var img = new Image();
+ img.srcset = "/images/green.svg 100w";
+ return img.decode().then(function(arg) {
+ assert_equals(arg, undefined);
+ });
+}, document.title + " Image with SVG srcset decodes with undefined.");
+
+promise_test(function(t) {
+ var img = new Image();
+ img.srcset = "/non/existent/path.png 100w";
+ var promise = img.decode();
+ return promise_rejects_dom(t, "EncodingError", promise);
+}, document.title + " Non-existent srcset fails decode.");
+
+promise_test(function() {
+ var img = new Image();
+ img.srcset = "/images/green.png 100w";
+ var first_promise = img.decode();
+ var second_promise = img.decode();
+ assert_not_equals(first_promise, second_promise);
+ return Promise.all([
+ first_promise,
+ second_promise
+ ]);
+}, document.title + " Multiple decodes for images with srcset succeed.");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html
new file mode 100644
index 0000000000..5c68de29e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-detached.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Detached image blocks load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var img_loaded = false;
+
+var img = new Image();
+img.onload = function() {
+ img_loaded = true;
+};
+img.src = "/images/blue.png?pipe=trickle(d2)";
+
+test(function() {
+ assert_false(img_loaded);
+}, "setting img.src is async");
+
+async_test(function(t) {
+ document.addEventListener("DOMContentLoaded", t.step_func_done(function() {
+ assert_false(img_loaded);
+ }));
+}, "DOMContentLoaded doesn't wait for images");
+
+async_test(function(t) {
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_true(img_loaded);
+ }));
+}, "load waits for images");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html
new file mode 100644
index 0000000000..7b61606c47
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event-until-move-to-empty-source.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Inline image element blocks load until source is changed to empty source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<img src="/images/blue.png?pipe=trickle(d100)">
+<script>
+
+async_test(t => {
+ const image = document.querySelector("img");
+
+ assert_false(image.complete, "The image is loading initially");
+
+ // Complete the test as soon as we obtained the window "load" event,
+ // which should happen as soon as the image stops loading by moving
+ // to an empty source.
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(image.complete, "The image is no longer loading once the window 'load' event is dispatched");
+ }));
+
+ // Stop loading the image.
+ image.src = "";
+}, "Image element delays window's load event until the image changes to empty source");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html
new file mode 100644
index 0000000000..ac0cf29d3f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/delay-load-event.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Inline image element blocks load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var img_loaded = false;
+</script>
+<img src="/images/blue.png?pipe=trickle(d2)" onload="img_loaded = true;">
+<script>
+test(function() {
+ assert_false(img_loaded);
+}, "script execution doesn't wait for the image to load");
+
+async_test(function(t) {
+ document.addEventListener("DOMContentLoaded", t.step_func_done(function() {
+ assert_false(img_loaded);
+ }));
+}, "DOMContentLoaded doesn't wait for images");
+
+async_test(function(t) {
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_true(img_loaded);
+ }));
+}, "Image element delays window's load event");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html
new file mode 100644
index 0000000000..fe6d79fe2a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/disconnected-image-loading-lazy.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test(t => {
+ x = new Image();
+ x.loading = "auto";
+ x.src = "resources/image.png?auto";
+ x.onload = t.step_func_done();
+ t.step_timeout(t.unreached_func("Disconnected loading=auto image loads " +
+ "successfully, and doesn't timeout"), 2000);
+}, "loading=auto for disconnected image");
+
+async_test(t => {
+ x = new Image();
+ x.loading = "eager";
+ x.src = "resources/image.png?eager";
+ x.onload = t.step_func_done();
+ t.step_timeout(t.unreached_func("Disconnected loading=eager image loads " +
+ "successfully, and doesn't timeout"), 2000);
+}, "loading=eager for disconnected image");
+
+async_test(t => {
+ x = new Image();
+ x.loading = "lazy";
+ x.src = "resources/image.png?lazy";
+ x.onload = t.unreached_func("Disconnected loading=lazy image loads lazily.");
+ t.step_timeout(t.step_func_done(), 2000);
+}, "loading=lazy for disconnected image");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html
new file mode 100644
index 0000000000..ea63114d57
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Document base URL adopted img test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<link rel="match" href="document-base-url-ref.html">
+<base href="resources/" />
+<iframe></iframe>
+<script>
+ var iframe = document.querySelector('iframe');
+ var i = iframe.contentDocument.createElement('img');
+ i.src = "cat.jpg";
+ document.body.appendChild(i);
+ iframe.remove();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html
new file mode 100644
index 0000000000..6e55b21ff0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Document base URL img test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<img src="resources/cat.jpg" alt="cat">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html
new file mode 100644
index 0000000000..074209cc04
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-base-url.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Document base URL img test</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element" />
+<link rel="match" href="document-base-url-ref.html">
+<base href="resources/" />
+<img src="cat.jpg" alt="cat">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html
new file mode 100644
index 0000000000..da43099f71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/document-destroyed-crash.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<title>&lt;img> loading in destroyed document</title>
+<iframe></iframe>
+<script>
+onload = function() {
+ const img = new Image();
+ img.onload = function() {
+ const iframe = document.querySelector('iframe');
+ iframe.contentDocument.createElement('div').innerHTML =
+ `<picture>
+ <source srcset="nonexistent.png">
+ <img src="data:image/gif;base64,R0lGODlhCgAKAIAAAP/MAAAAACH5BAAAAAAALAAAAAAKAAoAAAIIhI+py+0PYysAOw==">
+ </picture>`;
+ iframe.remove();
+ };
+ img.src = 'data:image/gif;base64,R0lGODlhCgAKAIAAAP/MAAAAACH5BAAAAAAALAAAAAAKAAoAAAIIhI+py+0PYysAOw==';
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html
new file mode 100644
index 0000000000..0f7ab9ae27
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/iframed.sub.html
@@ -0,0 +1,78 @@
+<!doctype html>
+
+<img
+data-desc="img (no src)"
+data-narrow=""
+data-wide=""
+data-no-change>
+
+<img src=""
+data-desc="img (empty src)"
+data-narrow=""
+data-wide=""
+data-no-change>
+
+<img src="/images/broken.png?30-{{GET[id]}}"
+data-desc="img (src only) broken image"
+data-narrow="/images/broken.png?30-{{GET[id]}}"
+data-wide="/images/broken.png?30-{{GET[id]}}"
+data-no-change>
+
+<img src="/images/green-1x1.png?40-{{GET[id]}}"
+data-desc="img (src only) valid image"
+data-narrow="/images/green-1x1.png?40-{{GET[id]}}"
+data-wide="/images/green-1x1.png?40-{{GET[id]}}"
+data-no-change>
+
+<img srcset="/images/broken.png?50-{{GET[id]}}"
+data-desc="img (srcset 1 cand) broken image"
+data-narrow="/images/broken.png?50-{{GET[id]}}"
+data-wide="/images/broken.png?50-{{GET[id]}}"
+data-no-change>
+
+<img srcset="/images/green-1x1.png?60-{{GET[id]}}"
+data-desc="img (srcset 1 cand) valid image"
+data-narrow="/images/green-1x1.png?60-{{GET[id]}}"
+data-wide="/images/green-1x1.png?60-{{GET[id]}}"
+data-no-change>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/broken.png?70-{{GET[id]}}">
+<img src="/images/broken.png?71-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) broken image, img broken image"
+data-narrow="/images/broken.png?70-{{GET[id]}}"
+data-wide="/images/broken.png?71-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/broken.png?80-{{GET[id]}}">
+<img src="/images/green-2x2.png?81-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) broken image, img valid image"
+data-narrow="/images/broken.png?80-{{GET[id]}}"
+data-wide="/images/green-2x2.png?81-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/green-1x1.png?90-{{GET[id]}}">
+<img src="/images/broken.png?91-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) valid image, img broken image"
+data-narrow="/images/green-1x1.png?90-{{GET[id]}}"
+data-wide="/images/broken.png?91-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/green-1x1.png?100-{{GET[id]}}">
+<img src="/images/green-2x2.png?101-{{GET[id]}}"
+data-desc="picture: source (max-width:500px) valid image, img valid image"
+data-narrow="/images/green-1x1.png?100-{{GET[id]}}"
+data-wide="/images/green-2x2.png?101-{{GET[id]}}">
+</picture>
+
+<picture>
+<source media="(max-width:500px)" srcset="/images/green-1x1.png?110-{{GET[id]}}">
+<img src="/images/green-1x1.png?110-{{GET[id]}}"
+data-desc="picture: same URL in source (max-width:500px) and img"
+data-narrow="/images/green-1x1.png?110-{{GET[id]}}"
+data-wide="/images/green-1x1.png?110-{{GET[id]}}"
+data-no-change>
+</picture>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html
new file mode 100644
index 0000000000..f6ae65708c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<title>img viewport change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<style>
+.narrow { width:50px }
+.wide { width:1000px }
+</style>
+<div id=log></div>
+<script>
+setup({explicit_done:true});
+
+function resolve(url) {
+ if (url === "") {
+ return url;
+ }
+ var a = document.createElement('a');
+ a.href = url;
+ return a.href;
+}
+
+function insertIframe(className) {
+ var iframe = document.createElement('iframe');
+ iframe.className = className;
+ iframe.src = 'iframed.sub.html?id=' + token();
+ document.body.appendChild(iframe);
+}
+insertIframe('narrow');
+insertIframe('wide');
+
+var start_date = new Date();
+
+onload = function() {
+ var load_time = new Date() - start_date;
+ var iframes = document.getElementsByTagName('iframe');
+ [].forEach.call(iframes, function(iframe) {
+ [].forEach.call(iframe.contentDocument.images, function(img) {
+ var expected = {wide:resolve(img.dataset.wide), narrow:resolve(img.dataset.narrow)};
+ var current = iframe.className;
+ var next = current === 'wide' ? 'narrow' : 'wide';
+ var expect_change = expected[next].indexOf('broken.png') === -1 && !('noChange' in img.dataset);
+
+ test(function() {
+ assert_equals(img.currentSrc, expected[current]);
+ }, img.dataset.desc + ', onload, ' + current);
+
+ async_test(function() {
+ img.onload = this.unreached_func('Got unexpected load event');
+ img.onerror = this.unreached_func('Got unexpected error event');
+ if (expect_change) {
+ img.onload = this.step_func_done(function() {
+ assert_equals(img.currentSrc, expected[next]);
+ });
+ } else {
+ setTimeout(this.step_func_done(), 500 + load_time);
+ }
+ }, img.dataset.desc + ', resize to ' + next);
+ });
+ iframe.classList.toggle('wide');
+ iframe.classList.toggle('narrow');
+ });
+ done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js
new file mode 100644
index 0000000000..7c4e121b7c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/historical-progress-event.window.js
@@ -0,0 +1,16 @@
+async_test(t => {
+ const img = new Image();
+ t.add_cleanup(() => img.remove());
+ img.onloadstart = img.onprogress = img.onloadend = t.unreached_func("progress event fired");
+ img.onload = t.step_func_done(e => {
+ assert_true(e instanceof Event);
+ assert_false(e instanceof ProgressEvent);
+ });
+ img.src = "/images/rrgg-256x256.png";
+ document.body.append(img);
+}, "<img> does not support ProgressEvent or loadstart/progress/loadend");
+
+test(t => {
+ assert_equals(document.body.onloadend, undefined);
+ assert_equals(window.onloadend, undefined);
+}, "onloadend is not exposed");
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpg
new file mode 100644
index 0000000000..2fb0255609
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-1.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html
new file mode 100644
index 0000000000..932cd92b41
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-base-url.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Image load parses URL after microtask runs</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+// See https://github.com/whatwg/html/issues/7383 and
+// https://chromium-review.googlesource.com/c/chromium/src/+/3311225.
+// This test asserts two things:
+// 1.) That Document base URL modifications that take place in between an
+// image loading microtask being scheduled and executed are reflected in
+// the final image request
+// 2.) That subsequent changes to a Document's base URL before an image is
+// inserted into the DOM do not lead to the image being refetched when it
+// is inserted asynchronously later. This is because image insertion is
+// not a relevant mutation
+// (https://html.spec.whatwg.org/#relevant-mutations).
+promise_test(async t => {
+ const image = new Image();
+ image.src = 'green.png';
+
+ // Dynamically insert a <base> tag that should influence the above image
+ // request because the above code triggers a microtask to continue fetching
+ // the image, which will run while we await `loadPromise` below.
+ const base = document.createElement('base');
+ base.setAttribute('href', 'resources/');
+ document.head.append(base);
+
+ const loadPromise = new Promise((resolve, reject) => {
+ image.addEventListener('load', e => {
+ resolve();
+ }, {once: true});
+
+ image.addEventListener('error', e => {
+ reject('The image must load');
+ }, {once: true});
+ });
+
+ // The image should load successfully, since its request was influenced by the
+ // <base> element which points the request to the right directory.
+ await loadPromise;
+
+ // Now manipulate the <base> element to point to a bogus directory.
+ base.setAttribute('href', 'bogus/');
+ document.body.append(image);
+
+ const timeoutPromise = new Promise(resolve => t.step_timeout(resolve, 1500));
+ const imageErrorPromise = new Promise((resolve, reject) => {
+ image.addEventListener('load', e => {
+ reject('The image should not be refetched upon insertion and load, ' +
+ 'because (1) insertion is not a relevant mutation, and (2) the ' +
+ 'new relative URL should not resolve to a real resource');
+ }, {once: true});
+
+ image.addEventListener('error', e => {
+ reject('The image should not be refetched upon insertion, because ' +
+ 'insertion is not a relevant mutation');
+ }, {once: true});
+ });
+
+ await Promise.race([timeoutPromise, imageErrorPromise]);
+}, "An image should not be refetched upon insertion asynchronously after its " +
+ "Document's base URL changes");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html
new file mode 100644
index 0000000000..ea80d8b545
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<style>
+#change {
+ height:75px;
+ width:75px;
+}
+</style>
+<img id="change" src="/images/green-16x16.png"></img>
+<img src="/images/green-16x16.png"></img>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html
new file mode 100644
index 0000000000..d3e7ee4171
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-change.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Composited images correctly re-raster when the image and bounds change</title>
+<meta charset="utf-8">
+<meta name=fuzzy content="maxDifference=0-150;totalPixels=0-296">
+<link rel="match" href="image-compositing-change-ref.html"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<style>
+#change {
+ will-change:transform;
+ height:426px; width:426px;
+}
+</style>
+<img id="change" src="image.png"></img>
+<img id="original" src="../../../../images/green-16x16.png"></img>
+<script>
+window.onload = () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ let image = document.querySelector('#change');
+ image.style.width = image.style.height = "75px";
+ image.src = original.src;
+
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+ });
+}
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html
new file mode 100644
index 0000000000..852a47687e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<style>
+html { overflow: hidden; }
+#change {
+ will-change:transform;
+ width:200vw;
+ height:200vh;
+ position:absolute;
+ top: 0px;
+ left: 0px;
+}
+</style>
+<img id="change" src="image.png"></img>
+<div id="placeholder" style="position:relative">div</div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html
new file mode 100644
index 0000000000..515f88e3f4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-compositing-large-scale-change.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Composited images correctly display under large scale transform changes</title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="match" href="image-compositing-large-scale-change-ref.html"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<style>
+html { overflow: hidden; }
+#change {
+ will-change:transform;
+ width:1000px;
+ height:1000px;
+ position:absolute;
+ top: calc(50% - 5px);
+ left: calc(50% - 5px);
+}
+</style>
+<img id="change" src="image.png"></img>
+<div id="placeholder" style="position:relative"></div>
+<script>
+window.onload = () => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ let image = document.querySelector('#change');
+ image.style.transform = 'scale(20)';
+ placeholder.innerText = "div";
+
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+ });
+ });
+}
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html
new file mode 100644
index 0000000000..54e169f867
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-eager.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='eager' load immediately regardless of their
+ position with respect to the viewport</title>
+ <link rel="author" title="Scott Little" href="mailto:sclittle@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that images with loading='eager' load " +
+ "immediately regardless of their position with " +
+ "respect to the viewport.");
+
+ let has_in_viewport_loaded = false;
+ const in_viewport_img_onload = t.step_func(() => {
+ assert_false(has_in_viewport_loaded,
+ "The in_viewport element should load only once.");
+ has_in_viewport_loaded = true;
+ });
+
+ let has_below_viewport_loaded = false;
+ const below_viewport_img_onload = t.step_func(() => {
+ assert_false(has_below_viewport_loaded,
+ "The below_viewport element should load only once.");
+ has_below_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "The in_viewport element should have loaded before window.load().");
+ assert_true(has_below_viewport_loaded,
+ "The below_viewport element should have loaded before window.load().");
+ }));
+
+</script>
+
+<body>
+ <img id="in_viewport" src="resources/image.png?in-viewport" loading="eager" onload="in_viewport_img_onload();">
+ <div style="height:10000px;"></div>
+ <!-- The below_viewport element loads very slowly in order to ensure that the
+ window load event is blocked on it. -->
+ <img id="below_viewport"
+ src="resources/image.png?below-viewport&pipe=trickle(d2)"
+ loading="eager" onload="below_viewport_img_onload();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html
new file mode 100644
index 0000000000..1e58c43c86
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-available.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>The list of available images gets checked before deciding to make a load lazy</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#will-lazy-load-image-steps">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1709577">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img src="/images/green-256x256.png">
+<div style="height:1000vh;"></div>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => {
+ window.addEventListener("load", resolve);
+ });
+ let nonLazy = document.querySelector("img");
+ assert_equals(nonLazy.width, 256);
+ assert_equals(nonLazy.height, 256);
+
+ let lazy = document.createElement("img");
+ lazy.loading = "lazy";
+ lazy.src = nonLazy.src;
+ document.body.appendChild(lazy);
+
+ await new Promise(resolve => setTimeout(resolve));
+
+ assert_equals(lazy.width, 256, "The list of available images should be checked before delaying the image load");
+ assert_equals(lazy.height, 256, "The list of available images should be checked before delaying the image load");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html
new file mode 100644
index 0000000000..e3a4a5f96e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url-2.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred loading=lazy images load relative to the document's base URL
+ at parse-time</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const below_viewport_img = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // Change the document's base URL to a bogus one, and scroll the
+ // below-viewport img into view. When it loads, it should load relative
+ // to the old base URL computed at parse-time.
+ window.addEventListener("load", t.step_func(() => {
+ window.history.pushState(2, document.title,
+ '/invalid-url-where-no-subresources-exist/')
+ has_window_loaded = true;
+ below_viewport_img.element().scrollIntoView();
+ }));
+
+ below_viewport_img.promise.then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy images do not block the " +
+ "window load event");
+ }));
+
+ below_viewport_img.promise.catch(
+ t.unreached_func("The image request should not load relative to the " +
+ "current (incorrect) base URL.")
+ );
+ }, "When a loading=lazy image is loaded, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <script>
+ // Change the document's base URL so that the img request parses relative
+ // to it when it sets up the request at parse-time.
+ window.history.pushState(1, document.title, 'resources/')
+ </script>
+ <img id="below-viewport" src="image.png?base-url-2" loading="lazy"
+ onload="below_viewport_img.resolve()"
+ onerror="below_viewport_img.reject()">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html
new file mode 100644
index 0000000000..01ce961d0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-base-url.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred images with loading='lazy' use the original
+ base URL specified at parse-time</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+ <base href='/html/semantics/embedded-content/the-img-element/resources/'>
+</head>
+
+<script>
+ const below_viewport_img = new ElementLoadPromise("below-viewport");
+
+ let has_window_loaded = false;
+
+ async_test(t => {
+ // At this point, the below-viewport image's request has been set-up, and
+ // its URL is the URL that was parsed relative to the document's base URL
+ // at this time. Any changes to the document's base URL from this point
+ // forward should not impact the image when we scroll it in-view. This is
+ // because the next step in the #updating-the-img-data algorithm is to to
+ // fetch the request that has already been set up. Now we'll change the
+ // document's base URL, and scroll the image in-view.
+ window.addEventListener("load", t.step_func(() => {
+ const base = document.querySelector('base');
+ base.href = '/invalid-url-where-no-subresources-exist/';
+ has_window_loaded = true;
+ below_viewport_img.element().scrollIntoView();
+ }));
+
+ below_viewport_img.promise.then(t.step_func_done(() => {
+ assert_true(has_window_loaded,
+ "Below-viewport loading=lazy images do not block the " +
+ "window load event");
+ }));
+
+ below_viewport_img.promise.catch(
+ t.unreached_func("The image request should not load relative to the " +
+ "current (incorrect) base URL.")
+ );
+ }, "When a loading=lazy image is loaded, it loads relative to the " +
+ "document's base URL computed at parse-time.");
+</script>
+
+<body>
+ <div style="height:1000vh"></div>
+ <img id="below-viewport" src="image.png?base-url" loading="lazy"
+ onload="below_viewport_img.resolve()"
+ onerror="below_viewport_img.reject()">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html
new file mode 100644
index 0000000000..78f18f0c23
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-below-viewport-dynamic.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+ <title>Below viewport images with loading='lazy' and changed to
+ loading='eager' load and do not block the window load event</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that below viewport images with loading='lazy' " +
+ "and changed to loading='eager' load and do not block " +
+ "the window load event.");
+
+ let has_below_viewport_loaded = false;
+ let has_window_loaded = false;
+
+ window.addEventListener("load", t.step_func(function() {
+ assert_false(has_window_loaded,
+ "The window load event should only fire once.");
+ has_window_loaded = true;
+ }));
+
+ const below_viewport_img_onload = t.step_func_done(function() {
+ assert_false(has_below_viewport_loaded,
+ "The in_viewport element should load only once.");
+ assert_true(has_window_loaded,
+ "The window load event should have fired before " +
+ "below_viewport loaded.");
+ has_below_viewport_loaded = true;
+ });
+</script>
+
+<body>
+ <div style="height:10000px;"></div>
+ <img id="below_viewport" src="resources/image.png?below-viewport-dynamic&pipe=trickle(d2)"
+ loading="lazy" onload="below_viewport_img_onload();">
+ <script>
+ assert_false(has_window_loaded,
+ "The window load event should not fire before " +
+ "changing below_viewport to loading='eager'.");
+ document.getElementById("below_viewport").loading = 'eager';
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html
new file mode 100644
index 0000000000..05a60034ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="resources/image.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html
new file mode 100644
index 0000000000..55f134a701
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-clip-path.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+<link rel="help" href="https://crbug.com/1308299">
+<link rel="match" href="image-loading-lazy-clip-path-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<img id=target loading="lazy"
+ src="resources/image.png"
+ style="vertical-align: middle; clip-path: polygon(0 0, 110% 0, 110% 110%, 0 110%, 0 0)">
+<script>
+ target.onload = takeScreenshot;
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html
new file mode 100644
index 0000000000..84efc7b0d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-crossorigin-change.sub.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred images with loading='lazy' use the latest crossorigin attribute</title>
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const img = new ElementLoadPromise("cross-origin");
+
+ async_test(function(t) {
+ window.addEventListener("load", t.step_func(() => {
+ // At this point the image's #updating-the-image-data algorithm has been
+ // invoked, and the image request has been deferred. The deferred
+ // cross-origin image request was created with the `no-cors` request mode,
+ // which would succeed to load the cross-origin image.
+ // While the request is deferred, we'll set the `crossorigin` attribute to a
+ // value that would cause the image request to fail. Since `crossorigin`
+ // mutations trigger another #updating-the-image-data invocation (replacing
+ // the first one), when we scroll the image into view, the image should be
+ // fetched with the latest `crossorigin` attribute value, and fail to load.
+ img.element().crossOrigin = 'anonymous';
+ img.element().scrollIntoView();
+ }));
+
+ img.promise
+ .then(t.unreached_func("The image should not load."))
+ .catch(t.step_func(() => { img.element().onload = t.step_func_done(); img.element().src = 'resources/image.png'; }));
+ }, "Test that when deferred image is loaded, it uses the latest crossorigin attribute.");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="cross-origin" loading="lazy"
+ src='http://{{hosts[alt][]}}:{{ports[http][0]}}/html/semantics/embedded-content/the-img-element/resources/image.png'
+ onload="img.resolve();" onerror="img.reject();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html
new file mode 100644
index 0000000000..05a60034ad
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img src="resources/image.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html
new file mode 100644
index 0000000000..809068dd05
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-data-url-to-https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <title>Lazy loaded Images with data url placeholders can be overwritten by a src change</title>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+ <link rel="match" href="image-loading-lazy-data-url-to-https-ref.html">
+ <script src="/common/reftest-wait.js"></script>
+</head>
+
+<body>
+ <img id="image" loading="lazy" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 872 490' width='872' height='490' style='background: green' %3E%3C/svg%3E">
+
+<script>
+ const image = document.querySelector('#image');
+
+ window.onload = function() {
+ // trigger intersection observer through forced layout.
+ image.offsetWidth;
+ image.setAttribute("src", 'resources/image.png');
+ setTimeout(() => { takeScreenshot(); }, 100);
+ };
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html
new file mode 100644
index 0000000000..1ea46ffcc5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-different-crossorigin.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<html>
+<title>Lazyload images cannot load immediately from the list of available images if their tuple doesn't match other images in that list</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="author" title="Przemyslaw Gorszkowski" href="mailto:pgorszkowski@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- A `loading=lazy` image will be placed below this div so that it is below
+ the viewport -->
+<div id="img-container"></div>
+<div style="height: 1000vh;"></div>
+<div id="below-viewport-img-container"></div>
+
+<script>
+const image_path = 'resources/image.png?image-lazy-loading-lazy-different-crossorigin-' + Math.random();
+
+promise_test(async t => {
+ await new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject(new Error("The img should not fail to load")) };
+ document.querySelector('#img-container').append(img);
+ img.src = image_path;
+ });
+
+
+ // At this point, the image fetched eagerly above exists in the "list of
+ // available images". As per the spec's #updating-the-image-data algorithm [1]
+ // step 6, the "list of avalable images" is consulted before we take any
+ // lazyload-specific action. This means that lazyload images can load eagerly
+ // if they target a resource in the list of available images (includes cors
+ // settings attribute matching [2]). The image from "img-container" does not
+ // have "crossorigin" attribute and the lazyload image has crossorigin="anonymous".
+ // This means that lazyload image cannot be loaded eagerly from the list of available images.
+ // [1]: https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
+ // [2]: https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attribute
+ const lazyload_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.loading = 'lazy';
+ img.crossOrigin = "anonymous";
+ img.onload = e => {
+ reject("The img should not be loaded from the list of available images because of different 'crossorigin'") };
+ img.onerror = e => { reject("The img should not fail to load") };
+
+ document.querySelector('#below-viewport-img-container').append(img);
+ img.src = image_path;
+ });
+ const timeout_promise = new Promise((resolve, reject) => {
+ t.step_timeout(() => {
+ resolve("The `loading=lazy` image should not load immediately from " +
+ "the list of available images because of different 'crossorigin'");
+ }, 2000);
+ });
+
+ // The `timeout_promise` should resolve first because lazyload image is not
+ // able to eagerly use resource from the "list of available images" if there
+ // is a difference in 'crossorigin'.
+ await Promise.race([lazyload_image_promise, timeout_promise]);
+}, "Lazyload images cannot load immediately from the list of available images if their tuple doesn't match other images in that list");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html
new file mode 100644
index 0000000000..2a0aefea1d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-empty-src.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<title>Lazy loaded Images handle correctly when setting src to empty</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<div id=log></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img id="image" loading="lazy" src="resources/image.png">
+
+<script>
+ const image = document.querySelector('#image');
+
+async_test(function(t) {
+ image.onerror = t.step_func(function(e) {
+ assert_equals(e.type, "error", "null image source check failed");
+ image.onload = t.step_func(function() {
+ t.done();
+ });
+ image.src = "resources/image.png";
+ });
+ image.src = "";
+}, "lazy loaded image and empty src");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html
new file mode 100644
index 0000000000..eed3644650
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-001.sub.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<head>
+<title>A below-viewport loading=lazy image in a cross origin iframe loads only
+ when scrolled into viewport</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+
+<iframe id="iframe" width="500px" height="500px"></iframe>
+
+<script>
+promise_test(t => {
+ iframe.src =
+ get_host_info().HTTP_NOTSAMESITE_ORIGIN +
+ new URL("resources/", self.location).pathname +
+ "image-loading-lazy-below-viewport.html";
+
+ // Wait for the frame to report that its window load event fired.
+ return new Promise(resolve => {
+ window.addEventListener("message",
+ event => resolve(event.data), {once: true});
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "window_loaded",
+ "The loading=lazy image should not block the iframe's load " +
+ "event");
+
+ // Tell the iframe to scroll the image element into view.
+ frames[0].postMessage("scroll", "*");
+
+ return new Promise(resolve => {
+ window.addEventListener("message", event => resolve(event.data));
+ });
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "image_loaded",
+ "The below-viewport loading=lazy image should load only " +
+ "once scrolled into the viewport");
+
+ }); // new Promise();
+}); // promise_test.
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html
new file mode 100644
index 0000000000..85060d2193
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-cross-origin-iframe-002.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+<title>A loading=lazy image in a below-viewport cross-origin iframe loads only
+ when the cross-origin iframe is scrolled into view</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" width="500px" height="500px"></iframe>
+
+<script>
+promise_test(t => {
+ iframe.src =
+ get_host_info().HTTP_NOTSAMESITE_ORIGIN +
+ new URL("resources/", self.location).pathname +
+ "image-loading-lazy-in-viewport.html";
+
+ // Wait for the frame to report that its window load event fired.
+ return new Promise(resolve => {
+ window.addEventListener("message",
+ event => resolve(event.data), {once: true});
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "window_loaded",
+ "The loading=lazy image should not block the iframe's load " +
+ "event");
+
+ // Scroll the iframe into view, which also puts the image into view.
+ iframe.scrollIntoView();
+
+ return new Promise(resolve => {
+ window.addEventListener("message", event => resolve(event.data));
+ });
+ }).then(iframe_message => {
+ assert_equals(iframe_message, "image_loaded",
+ "The below-viewport loading=lazy image should load only " +
+ "once scrolled into the viewport");
+
+ }); // new Promise().
+
+}); // promise_test().
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html
new file mode 100644
index 0000000000..fbcadd86c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-script-disabled-iframe.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+<title>Images with loading='lazy' in script disabled iframe are not handled
+ as 'lazy'</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" sandbox="allow-same-origin"
+ src="resources/image-loading-lazy-in-viewport.html">
+</iframe>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => iframe.addEventListener("load", resolve));
+
+ const image = iframe.contentDocument.querySelector("img");
+
+ assert_true(image.complete,
+ "lazy-load image shouldn't be honored in script disabled iframe");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html
new file mode 100644
index 0000000000..065fa939bc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-far.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 10000vh;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images do not load when far from viewport."
+ );
+
+ function img_onload() {
+ t.unreached_func(
+ "Lazy-loading image far from viewport should not load."
+ )();
+ }
+
+ t.step_timeout(() => {
+ t.done();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html
new file mode 100644
index 0000000000..20274310f1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal-far.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #spacer {
+ width: 10000vw;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images do not load when far from viewport."
+ );
+
+ function img_onload() {
+ t.unreached_func(
+ "Lazy-loading image far from viewport should not load."
+ )();
+ }
+
+ t.step_timeout(() => {
+ t.done();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html
new file mode 100644
index 0000000000..124dbaddf8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-horizontal.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html
new file mode 100644
index 0000000000..5e25f45275
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-2.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div id="spacer"></div>
+ <div id="scroller">
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html
new file mode 100644
index 0000000000..0d6d95826b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-3.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #scroller3 {
+ width: 120px;
+ height: 120px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller3>
+ <div id=scroller2>
+ <div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html
new file mode 100644
index 0000000000..312e90bd49
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-4.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div class="spacer"></div>
+ <div id="scroller">
+ <div class="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html
new file mode 100644
index 0000000000..77bb3004d3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested-5.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ display: flex;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ .spacer {
+ width: 130px;
+ height: 130px;
+ flex-shrink: 0;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 0;
+ }
+</style>
+
+<div id=scroller2>
+ <div class="spacer"></div>
+ <div id="scroller">
+ <div class="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html
new file mode 100644
index 0000000000..cc3e4d5e4b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller-nested.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #scroller2 {
+ width: 110px;
+ height: 110px;
+ overflow: scroll;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id=scroller2>
+ <div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+ </div>
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html
new file mode 100644
index 0000000000..17f0e7d17a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-scroller.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-root-margin">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #scroller {
+ width: 100px;
+ height: 100px;
+ overflow: scroll;
+ background-color: gray;
+ }
+
+ #spacer {
+ width: 130px;
+ height: 130px;
+ }
+
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+
+<div id="scroller">
+ <div id="spacer"></div>
+ <img
+ id="target"
+ src="resources/green.png"
+ loading="lazy"
+ onload="img_onload();"
+ >
+</div>
+
+<script>
+ const t = async_test(
+ "Test that lazy-loaded images load when near viewport."
+ );
+
+ function img_onload() {
+ t.done();
+ }
+
+ t.step_timeout(() => {
+ t.unreached_func(
+ "Timed out while waiting for image to load."
+ )();
+ }, 2000);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html
new file mode 100644
index 0000000000..39dd5dc1e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-in-viewport-dynamic.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+ <title>In viewport images with loading='lazy' and changed to loading='eager'
+ do not block the window load event</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that in viewport images with loading='lazy' and " +
+ "changed to loading='eager' do not block the window " +
+ "load event.");
+
+ let has_in_viewport_loaded = false;
+ let has_window_loaded = false;
+
+ const in_viewport_img_onload = t.step_func_done(function() {
+ assert_false(has_in_viewport_loaded,
+ "The in_viewport element should load only once.");
+ assert_true(has_window_loaded,
+ "The window load event should fire before in_viewport image loads.");
+ has_in_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func(function() {
+ assert_false(has_window_loaded,
+ "The window load event should only fire once.");
+ has_window_loaded = true;
+ }));
+</script>
+
+<body>
+ <img id="in_viewport" src="resources/image.png?in-viewport-dynamic&pipe=trickle(d2)"
+ loading="lazy" onload="in_viewport_img_onload();">
+ <script>
+ document.getElementById("in_viewport").loading = 'eager';
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html
new file mode 100644
index 0000000000..ff7e83105c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-document.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<head>
+<title>Moving loading='lazy' image into another top level document</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<img loading="lazy"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC">
+<script>
+promise_test(async t => {
+ let image_loaded = false;
+ const img = document.querySelector("img");
+ img.addEventListener("load", () => { image_loaded = true; });
+
+ await new Promise(resolve => window.addEventListener("load", resolve));
+
+ assert_false(image_loaded,
+ "lazy-load image shouldn't be loaded yet");
+
+ const anotherWin = window.open("resources/newwindow.html");
+
+ await new Promise(resolve => anotherWin.addEventListener("load", resolve));
+
+ anotherWin.document.body.appendChild(img);
+
+ assert_false(image_loaded,
+ "lazy-load image shouldn't be loaded yet");
+
+ img.scrollIntoView();
+
+ await new Promise(resolve => img.addEventListener("load", resolve));
+ assert_true(img.complete,
+ "Now the lazy-load image should be loaded");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html
new file mode 100644
index 0000000000..79bfa24378
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-move-into-script-disabled-iframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<head>
+<title>A loading='lazy' image starts loading when the element is moved into
+ an iframe where script is disabled</title>
+<link rel="help" href="https://github.com/scott-little/lazyload">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+
+<div style="height:1000vh;"></div>
+<iframe id="iframe" src="resources/image-loading-lazy-in-viewport.html">
+</iframe>
+<iframe id="sandboxediframe" sandbox="allow-same-origin">
+</iframe>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => window.addEventListener('load', resolve));
+
+ const image = iframe.contentDocument.querySelector("img");
+
+ assert_false(image.complete, "lazy-load image shouldn't be loaded");
+
+ sandboxediframe.contentDocument.body.appendChild(image);
+ await new Promise(resolve => image.addEventListener("load", resolve));
+
+ assert_true(image.complete,
+ "lazy-load image shouldn't be honored in script disabled iframe");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html
new file mode 100644
index 0000000000..20d52d4dfa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multicol.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load when in the viewport</title>
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Test that images with loading='lazy' under multicol load once they enter the viewport.");
+
+ let has_in_viewport_loaded = false;
+ let has_window_loaded = false;
+
+ const in_viewport_img_onload = t.step_func(function() {
+ assert_false(has_in_viewport_loaded, "The in_viewport element should load only once.");
+ has_in_viewport_loaded = true;
+ });
+
+ window.addEventListener("load", t.step_func_done(function() {
+ assert_true(has_in_viewport_loaded, "The in_viewport element should have loaded before window.load().");
+ assert_false(has_window_loaded, "The window load event should only fire once.");
+ has_window_loaded = true;
+ }));
+
+</script>
+
+<div class=texty style="column-count: 2; height: 300px">
+ <div style="border: 1px solid black">
+ <h2 style="column-span: all"></h2>
+ <img loading="lazy" src="resources/image.png?loading-lazy-multicol-first" width="160" height="120"
+ onload="in_viewport_img_onload()">
+ </div>
+</div>
+
+ <!--
+ This async script loads very slowly in order to ensure that, if the
+ below_viewport element has started loading, it has a chance to finish
+ loading before window load event fires, so that the test will dependably fail
+ in that case instead of potentially passing depending on how long different
+ resource fetches take.
+ -->
+ <script async src="/common/slow.py"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html
new file mode 100644
index 0000000000..2d67150560
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-multiple-times.html
@@ -0,0 +1,60 @@
+<head>
+ <title>Images with loading='lazy' can be lazy loaded multiple times</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <!-- This is used to represent the top of the viewport, so we can scroll the
+ below-viewport image out-of-view later in the test -->
+ <div id="top_div"></div>
+ <div style="height:1000vh;"></div>
+ <img id="below_viewport" loading="lazy" src="resources/image.png?image-loading-lazy-multiple-times-first">
+
+<script>
+ const t = async_test("Images with loading='lazy' can be lazy loaded multiple times");
+ const image = document.querySelector('#below_viewport');
+ const top_div = document.querySelector('#top_div');
+
+ let has_window_load_fired = false;
+
+ // This should be triggered first.
+ window.addEventListener('load', t.step_func(() => {
+ has_window_load_fired = true;
+ // Scroll the loading=lazy below-viewport image into view, so that it loads.
+ image.scrollIntoView();
+ }));
+
+ image.onload = t.step_func(() => {
+ assert_true(has_window_load_fired,
+ "The loading=lazy below-viewport image should not block the " +
+ "window load event");
+ changeImageSourceAndScrollToTop();
+ });
+
+ function changeImageSourceAndScrollToTop() {
+ top_div.scrollIntoView();
+
+ // Allow some time for scroll back to top, since we don't
+ // want the image to still be in the viewport and trigger a
+ // load due to the scroll being slow.
+ t.step_timeout(() => {
+ // Lazily load a "different" image.
+ image.src = 'resources/image.png?image-loading-lazy-multiple-times-second';
+ image.onload =
+ t.unreached_func("The loading=lazy below-viewport image should lazily " +
+ "load its second image, and not load it eagerly when " +
+ "the `src` attribute is changed");
+
+ // In 1s, scroll the image *back* into view, and record that it loads
+ // successfully.
+ t.step_timeout(() => {
+ image.onload = t.step_func_done();
+ image.scrollIntoView();
+ }, 1000);
+ }, 500);
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html
new file mode 100644
index 0000000000..875160b4ea
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-negative-margin.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' defers images in a hidden area as a result
+ of negative margins</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+ <script>
+ window.negative_margin_test =
+ async_test("A loading=lazy image that is pulled into an `overflow: hidden` " +
+ "area by a negative margin will not load because " +
+ "IntersectionObserver sees it as non-intersecting");
+
+ // If the `negative_margin` image in the DOM loads, the test should fail
+ // immediately.
+ window.negative_margin_onload =
+ negative_margin_test.step_func_done(
+ negative_margin_test.unreached_func("The image with a negative margin " +
+ "should never load"));
+ </script>
+
+ <div style="width: 200px; height: 200px; overflow: hidden;">
+ <img id="negative_margin" width="5px"; style="margin-left: -10000vw;"
+ loading="lazy" src="resources/image.png?loading-lazy-negative-margin"
+ onload="window.negative_margin_onload()">
+ </div>
+
+ <script>
+ const intersection_observer_promise = new Promise(resolve => {
+ function io_callback(entries) {
+ assert_equals(entries.length, 1);
+ resolve(entries[0].isIntersecting);
+ }
+
+ const options = {
+ root: document,
+ rootMargin: '0px',
+ threshold: 1.0,
+ }
+
+ const observer = new IntersectionObserver(io_callback, options);
+ observer.observe(document.querySelector('#negative_margin'));
+ });
+
+ const timeout_promise = new Promise(resolve => {
+ window.negative_margin_test.step_timeout(resolve, 500);
+ });
+
+ Promise.all([intersection_observer_promise, timeout_promise]).then(
+ window.negative_margin_test.step_func_done(values => {
+ assert_equals(values.length, 2);
+ assert_equals(values[0], false, "The IntersectionObserver sees that " +
+ "the image does not intersect the " +
+ "viewport");
+ }));
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html
new file mode 100644
index 0000000000..110c36cca7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-referrerpolicy-change.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+ <title>Deferred loading=lazy images are fetched with the latest
+ `referrerpolicy` attribute</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+ const below_viewport_img = new ElementLoadPromise("below_viewport_img");
+
+ async_test(function(t) {
+ // At this point the image's #updating-the-image-data algorithm has been
+ // invoked, and the image request has been deferred. The deferred request
+ // was created with the default referrer policy, which will result in a
+ // `Referer` header being sent when the request is finally made. The request
+ // is also for an image that the server will send a broken response for if
+ // the request has a `Referer` header.
+ // While the request is deferred, we'll set the `referrerpolicy` attribute
+ // to `no-referrer`, which would cause the image request to succeed. Since
+ // `referrerpolicy` mutations trigger another #updating-the-image-data
+ // invocation (replacing the first one), when we scroll the image into view,
+ // the image should be fetched with no `Referer` header, and succeed.
+ window.addEventListener("load", t.step_func(() => {
+ below_viewport_img.element().referrerPolicy = "no-referrer";
+ below_viewport_img.element().scrollIntoView();
+ }));
+
+ below_viewport_img.promise
+ .then(t.step_func_done())
+ .catch(t.unreached_func("The image request should successfully load"))
+ }, "Test that when a deferred image is loaded, it uses the latest referrerpolicy");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="below_viewport_img"
+ src="resources/referrer-checker-img.py?expected_referrer="
+ loading="lazy" referrerpolicy="unsafe-url"
+ onload="below_viewport_img.resolve();"
+ onerror="below_viewport_img.reject();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html
new file mode 100644
index 0000000000..3a2662451e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-relevant-mutations.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<head>
+ <title>Relevant mutations on deferred loading=lazy images should not trigger
+ a request</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ let below_viewport_1_loaded = false,
+ below_viewport_2_loaded = false,
+ below_viewport_3_loaded = false
+
+ // For general lazy loading behavior.
+ promise_test(() => {
+ // When the page loads, start the rest of the tests.
+ return new Promise(resolve => {
+ window.addEventListener("load", e => {
+ const kAssertion = 'image should never load';
+ assert_false(below_viewport_1_loaded, `below-viewport-1 ${kAssertion}`);
+ assert_false(below_viewport_2_loaded, `below-viewport-2 ${kAssertion}`);
+ assert_false(below_viewport_3_loaded, `below-viewport-3 ${kAssertion}`);
+ resolve();
+ });
+ });
+ }, "Images are lazyloaded");
+
+ // For `referrerPolicy` attribute mutations.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const below_viewport_1 = document.querySelector('img#below-viewport-1');
+ below_viewport_1.onload = reject;
+ below_viewport_1.onerror = reject;
+ t.step_timeout(resolve, 1000);
+
+ below_viewport_1.referrerPolicy = 'no-referrer';
+ });
+ }, "Image referrerPolicy mutation does not cause deferred loading=lazy " +
+ "images to be fetched");
+
+ // For `crossOrigin` attribute mutations.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const below_viewport_2 = document.querySelector('img#below-viewport-2');
+ below_viewport_2.onload = reject;
+ below_viewport_2.onerror = reject;
+ t.step_timeout(resolve, 1000);
+
+ below_viewport_2.crossOrigin = 'anonymous';
+ });
+ }, "Image crossOrigin mutation does not cause deferred loading=lazy " +
+ "images to be fetched");
+
+ // For `src` attribute mutations.
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ const below_viewport_3 = document.querySelector('img#below-viewport-3');
+ below_viewport_3.onload = reject;
+ below_viewport_3.onerror = reject;
+ t.step_timeout(resolve, 1000);
+
+ below_viewport_3.src = "resources/image.png?relevant-mutations-change";
+ });
+ }, "Image src mutation does not cause deferred loading=lazy " +
+ "images to be fetched");
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="below-viewport-1" src="resources/image.png?relevant-mutations-1" loading="lazy"
+ onload="below_viewport_1_loaded = true"
+ onerror="below_viewport_1_loaded = true">
+
+ <img id="below-viewport-2" src="resources/image.png?relevant-mutations-2" loading="lazy"
+ onload="below_viewport_2_loaded = true"
+ onerror="below_viewport_2_loaded = true">
+
+ <img id="below-viewport-3" src="resources/image.png?relevant-mutations-3" loading="lazy"
+ onload="below_viewport_3_loaded = true"
+ onerror="below_viewport_3_loaded = true">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html
new file mode 100644
index 0000000000..6de01a9b3b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio-ref.html
@@ -0,0 +1,2 @@
+<!doctype HTML>
+<span style="display: inline-block; width: 100px; height: 50px; border: 1px solid black">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html
new file mode 100644
index 0000000000..662ada6909
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-aspect-ratio.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel="match" href="image-loading-lazy-slow-aspect-ratio-ref.html">
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+ <script src="/common/reftest-wait.js"></script>
+ <img id=target loading="lazy"
+ width="200" height="100" style="width: 100px; height: auto; border: 1px solid black">
+<script>
+ let loaded = false;
+ target.onload = () => {
+ if (loaded) return;
+ loaded = true;
+ target.src = "";
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ target.src = "resources/image.png?slow-aspect-ratio&pipe=trickle(d2)";
+ takeScreenshot();
+ }));
+ };
+ target.src = "resources/image.png?slow-aspect-ratio";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html
new file mode 100644
index 0000000000..20fbb9b50e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<span style="display: inline-block; width: 330px; height: 254px; border: 1px solid black"></span>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html
new file mode 100644
index 0000000000..fac2c2e8f7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-slow.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel="match" href="image-loading-lazy-slow-ref.html">
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+
+ <script src="/common/reftest-wait.js"></script>
+ <img id=target loading="lazy"
+ width="330" height="254" style="border: 1px solid black">
+<script>
+ let loaded = false;
+ target.onload = () => {
+ if (loaded) return;
+ loaded = true;
+ target.src = "";
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ target.src = "resources/image.png?loading-lazy-slow&pipe=trickle(d2)";
+ takeScreenshot();
+ }));
+ };
+ target.src = "resources/image.png?loading-lazy-slow";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html
new file mode 100644
index 0000000000..953d4af4ef
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-srcset.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<head>
+<title>loading='lazy' image with srcset</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#update-the-image-data">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#will-lazy-load-image-steps">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<div style="height:1000vh;"></div>
+<img srcset="resources/image.png?loading-lazy-srcset" loading="lazy">
+<img loading="lazy" srcset="resources/image.png?loading-lazy-srcset">
+<script>
+promise_test(async t => {
+ let loaded_images = 0;
+ const imgs = document.querySelectorAll("img");
+ imgs.forEach(img => {
+ img.addEventListener("load", () => { loaded_images++; }, { once: true });
+ });
+
+ await new Promise(resolve => window.addEventListener("load", resolve));
+
+ assert_equals(loaded_images, 0,
+ "lazy-load images with srcset shouldn't be loaded yet");
+
+ const promises = [
+ new Promise(resolve => imgs[0].addEventListener("load", resolve)),
+ new Promise(resolve => imgs[1].addEventListener("load", resolve)),
+ ];
+
+ imgs[1].scrollIntoView();
+ await Promise.all(promises);
+
+ imgs.forEach(img => {
+ assert_true(img.complete,
+ "Now the lazy-load image with srcset should be loaded");
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html
new file mode 100644
index 0000000000..86a290d50d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-subframe-detached-crash.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="test-wait">
+<title>Crash when detaching a frame during a lazy-load operation</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1619858">
+<iframe srcdoc=""></iframe>
+<script>
+onload = function() {
+ let frame = document.querySelector("iframe");
+ frame.contentDocument.body.innerHTML = `
+ <div style="height: 300vh"></div>
+ <img loading="lazy" src="/images/blue96x96.png" width=96 height=96>
+ `;
+ let img = frame.contentDocument.querySelector("img");
+ new IntersectionObserver(() => {
+ frame.remove();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.className = "";
+ });
+ });
+ }).observe(img);
+ frame.contentWindow.scrollTo(0, img.getBoundingClientRect().top);
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html
new file mode 100644
index 0000000000..6246063981
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-to-eager.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport images with loading='lazy' load when set to
+ loading='eager' or the `loading` attribute is removed</title>
+ <link rel="author" title="Dom Farolino" href="mailto:domfarolino@gmail.com">
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const t = async_test("Below-viewport images with loading='lazy' load when " +
+ "set to loading='eager' or the `loading` attribute is " +
+ "removed");
+
+ const img_1_onload = t.unreached_func("#img_1 should not load before the " +
+ "window load event");
+ const img_2_onload = t.unreached_func("#img_2 should not load before the " +
+ "window load event");
+
+ window.addEventListener("load", t.step_func(() => {
+ const img_1 = document.querySelector('#img_1');
+ const img_2 = document.querySelector('#img_2');
+
+ const img_1_promise = new Promise((resolve, reject) => {
+ img_1.onerror = reject;
+ img_1.onload = resolve;
+ });
+
+ const img_2_promise = new Promise((resolve, reject) => {
+ img_2.onerror = reject;
+ img_2.onload = resolve;
+ });
+
+ Promise.all([img_1_promise, img_2_promise])
+ .then(t.step_func_done())
+ .catch(t.unreached_func("The images should load successfully"));
+
+ // Kick off the requests.
+ img_1.loading = 'eager';
+ img_2.removeAttribute('loading'); // unset the attribute, putting it in
+ // the default (eager) state.
+ }));
+
+</script>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="img_1"
+ src="resources/image.png?lazy-to-eager-1"
+ loading="lazy" onload="img_1_onload();">
+ <img id="img_2"
+ src="resources/image.png?lazy-to-eager-2"
+ loading="lazy" onload="img_2_onload();">
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html
new file mode 100644
index 0000000000..f2a8a3542f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-use-list-of-available-images.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html>
+<title>Lazyload images can load immediately from the list of available images</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- A `loading=lazy` image will be placed below this div so that it is below
+ the viewport -->
+<div style="height: 1000vh;"></div>
+<div id="below-viewport-img-container"></div>
+
+<script>
+const image_path = location.origin + '/html/semantics/embedded-content/the-img-element/resources/image.png?image-loading-lazy-use-list-of-available-images-' + Math.random();
+
+promise_test(async t => {
+ const eager_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject(new Error("The img should not fail to load")) };
+ img.src = image_path;
+ });
+
+ await eager_image_promise;
+
+ // At this point, the image fetched eagerly above exists in the "list of
+ // available images". As per the spec's #updating-the-image-data algorithm [1]
+ // step 6, the "list of avalable images" is consulted before we take any
+ // lazyload-specific action. This means that lazyload images can load eagerly
+ // if they target a resource in the list of available images, which the image
+ // below is doing.
+ //
+ // Note that if https://github.com/whatwg/html/issues/7005 resolves in favor
+ // of allowing in-flight image requests to be placed in the list of available
+ // images, as opposed to just complete images, this would allow lazyload
+ // images (in addition to non-lazyload ones) to coalesce with these in-flight
+ // entries in the list of available images too. In that case we'd need to test
+ // for this here.
+ // [1]: https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
+ const lazyload_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.loading = 'lazy';
+ img.onload = resolve;
+ img.onerror = e => { reject("The img should not fail to load") };
+
+ document.querySelector('#below-viewport-img-container').append(img);
+ img.src = image_path;
+ });
+ const timeout_promise = new Promise((resolve, reject) => {
+ t.step_timeout(() => {
+ reject(new Error("The `loading=lazy` image should load immediately from " +
+ "the list of available images, beating this timeout " +
+ "promise."));
+ }, 1000);
+ });
+
+ // The `lazyload_image_promise` should resolve first because lazyload images
+ // are able to eagerly use resources in the "list of available images".
+ await Promise.race([lazyload_image_promise, timeout_promise]);
+}, 'Lazyload images can load immediately from the list of available images');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html
new file mode 100644
index 0000000000..9962ce7837
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy-zero-intersection-area.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Image with zero intersection area is lazy-loaded</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<link rel="help" href="https://html.spec.whatwg.org/#lazy-load-intersection-observer">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1785186">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div style="height: 0; overflow: hidden;">
+ <img style="display: block" id=target loading="lazy" width="100" height="100">
+</div>
+<script>
+ async_test(function(t) {
+ target.addEventListener("load", t.step_func_done(function() {}));
+ target.src = "resources/image.png?zero-intersection-area";
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html
new file mode 100644
index 0000000000..88f6549d96
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-lazy.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load only when in the viewport</title>
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <link rel="author" title="Scott Little" href="mailto:sclittle@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<script>
+ const in_viewport_test =
+ async_test("In-viewport loading=lazy images load immediately but do not " +
+ "block the window load event");
+ const below_viewport_test =
+ async_test("Below-viewport loading=lazy images only load when in the " +
+ "viewport and do not block the window load event");
+ const below_viewport_data_url_test =
+ async_test("Below-viewport data:url images only load when in the " +
+ "viewport and do not block the window load event");
+ const below_viewport_blob_url_test =
+ async_test("Below-viewport blob URL images only load when in the " +
+ "viewport and do not block the window load event");
+
+ document.addEventListener('DOMContentLoaded', e => {
+ const img = document.querySelector('#below_viewport_blob_url');
+
+ // Blob URL helper.
+ // Source: https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752.
+ function fixBinary(bin) {
+ const length = bin.length;
+ const buf = new ArrayBuffer(length);
+ const arr = new Uint8Array(buf);
+ for (var i = 0; i < length; i++) {
+ arr[i] = bin.charCodeAt(i);
+ }
+
+ return buf;
+ }
+
+ const base64 =
+ "R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA";
+ const binary = fixBinary(atob(base64));
+ const blob = new Blob([binary], {type: 'image/png'});
+ const url = URL.createObjectURL(blob);
+ img.src = url;
+ }) // DOMContentLoaded.
+
+ let has_window_load_fired = false;
+ let has_in_viewport_loaded = false;
+
+ window.onload = e => {
+ has_window_load_fired = true;
+ }
+
+ // Helper assertion messages for the below tests.
+ const kScrollAssertion = "images only load when scrolled into view";
+ const kWindowLoadAssertion = "images do not block the load event";
+
+ const in_viewport_img_onload = in_viewport_test.step_func_done(() => {
+ has_in_viewport_loaded = true;
+ document.querySelector('#bottom').scrollIntoView();
+ assert_true(has_window_load_fired,
+ "In-viewport loading=lazy images do not block the window " +
+ "load event");
+ });
+
+ const below_viewport_img_onload = below_viewport_test.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "Below-viewport loading=lazy images only load once loaded " +
+ "into the viewport");
+ assert_true(has_window_load_fired,
+ "Below-viewport loading=lazy images should not block the " +
+ "window load event");
+ });
+
+ const below_viewport_data_url_img_onload = below_viewport_data_url_test.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "Below-viewport loading=lazy data: url images " +
+ kScrollAssertion);
+ assert_true(has_window_load_fired,
+ "Below-viewport loading=lazy data: url images " +
+ kWindowLoadAssertion);
+ });
+
+ const below_viewport_blob_url_img_onload = below_viewport_blob_url_test.step_func_done(() => {
+ assert_true(has_in_viewport_loaded,
+ "Below-viewport loading=lazy blob url images " +
+ kScrollAssertion);
+ assert_true(has_window_load_fired,
+ "Below-viewport loading=lazy blob url images " +
+ kWindowLoadAssertion);
+ });
+</script>
+
+<body>
+ <!-- |in_viewport| takes 2 seconds to load, so that in browsers that don't
+ support lazy loading, |below_viewport| finishes before |in_viewport|, and
+ the test will dependably fail without relying on a timeout. -->
+ <img id="in_viewport" loading="lazy" src="resources/image.png?image-loading-lazy-first&pipe=trickle(d2)"
+ onload="in_viewport_img_onload()">
+ <div style="height:1000vh;"></div>
+ <img id="below_viewport" loading="lazy" src="resources/image.png?image-loading-lazy-second"
+ onload="below_viewport_img_onload()">
+ <img id="below_viewport_data_url" loading="lazy"
+ src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA"
+ onload="below_viewport_data_url_img_onload()">
+ <!-- This image has its `src` set to a blob URL dynamically above -->
+ <img id="below_viewport_blob_url" loading="lazy"
+ onload="below_viewport_blob_url_img_onload()">
+ <div id="bottom"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html
new file mode 100644
index 0000000000..f841dba31b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+* {
+ margin: 0;
+}
+</style>
+<html class="reftest-wait" style="overflow: hidden">
+ <head>
+ <title>Images with loading='lazy' load under subpixel-offset clips</title>
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/#lazy-loading-attributes">
+ </head>
+ <div style="height: 44.5px"></div>
+ <div style="position: relative; font-size: 0; background: lightblue">
+ <img id=target loading="lazy" data-sizes="auto">
+ </div>
+</html>
+<script src="/common/reftest-wait.js"></script>
+<script>
+ target.onload = takeScreenshot;
+ target.src = "resources/image.png";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html
new file mode 100644
index 0000000000..594d9bebe4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<style>
+* {
+ margin: 0;
+}
+</style>
+<html class="reftest-wait" style="overflow: hidden">
+ <head>
+ <title>Images with loading='lazy' load under subpixel-offset clips</title>
+ <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+ <link rel="help" href="https://html.spec.whatwg.org/#lazy-loading-attributes">
+ <link rel="match" href="image-loading-subpixel-clip-ref.html">
+ </head>
+ <div style="height: 44.5px"></div>
+ <div style="overflow: hidden">
+ <div style="position: relative; font-size: 0; background: lightblue">
+ <img id=target loading="lazy" data-sizes="auto">
+ </div>
+ </div>
+</html>
+<script src="/common/reftest-wait.js"></script>
+<script>
+ target.onload = takeScreenshot;
+ target.src = "resources/image.png";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html
new file mode 100644
index 0000000000..160f9f50a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<style>
+ body { margin: 0 }
+ img {
+ margin: 16px; /* to account for iframe + body margin in the test */
+ }
+</style>
+<img src="/images/green-100x50.png" alt="FAIL">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html
new file mode 100644
index 0000000000..4ad922ba00
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image-srcdoc-relative-uri-print.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1710822">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="image-srcdoc-relative-uri-print-ref.html">
+<iframe frameborder=0 srcdoc="<img src=/images/green-100x50.png>">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.png
new file mode 100644
index 0000000000..d26878c9f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/image.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html
new file mode 100644
index 0000000000..852375bff3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-created-in-active-document-crash.html
@@ -0,0 +1,6 @@
+<iframe id="i"></iframe>
+<script>
+var doc = i.contentDocument;
+i.remove();
+doc.createElement("img");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html
new file mode 100644
index 0000000000..3518cab54d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-picture-ancestor.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>img should only look at a parent picture element</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<picture>
+ <source media="not all" srcset="data:,a">
+ <source media="all" srcset="data:,b">
+ <img src="data:,c">
+ <picture>
+ <source media="not all" srcset="data:,e">
+ <source media="all" srcset="data:,f">
+ <img src="data:,g">
+ </picture>
+</picture>
+<script>
+const picture1 = document.querySelector("picture");
+const picture2 = document.querySelector("picture > picture");
+const img1 = document.querySelector("picture > img");
+const img2 = document.querySelector("picture > picture > img");
+
+const div = document.createElement("div");
+
+const imgInsideDiv = document.createElement("img");
+imgInsideDiv.src = "data:,d";
+div.append(imgInsideDiv);
+
+test(function() {
+ assert_equals(img1.currentSrc, "data:,b");
+}, "currentSrc of img in normally parented picture is correct");
+
+test(function() {
+ assert_equals(img2.currentSrc, "data:,f");
+}, "currentSrc of img in nested picture element is correct");
+
+async_test(function(t) {
+ picture1.append(div);
+ queueMicrotask(t.step_func(function() {
+ assert_equals(imgInsideDiv.currentSrc, "data:,d");
+ t.done();
+ }));
+}, "currentSrc of img with picture ancestor but non-picture parent is correct");
+
+async_test(function(t) {
+ picture2.remove();
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img2.currentSrc, "data:,f");
+ t.done();
+ }));
+}, "currentSrc of img in nested picture element remains correct when the inner picture is removed from the document");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html
new file mode 100644
index 0000000000..24c5567fb8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-src-in-synthetic-document.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/issues/9855">
+<link rel=help href="https://html.spec.whatwg.org/#reflecting-content-attributes-in-idl-attributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(() => {
+ const doc = document.implementation.createHTMLDocument('');
+ const img = doc.createElement('img');
+ img.setAttribute('src', '/test');
+ doc.body.appendChild(img);
+ assert_equals(img.src, '/test');
+}, 'HTMLImageElement.src should return the string from the attribute in about:blank documents.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html
new file mode 100644
index 0000000000..56176c4b71
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<style>
+img {
+ width: 200px;
+ height: 100px;
+}
+</style>
+<img src="image.png">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html
new file mode 100644
index 0000000000..bdeae0ac2e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img-with-containment-and-size.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Ensure images with containment and size are rendered properly</title>
+<meta charset="utf-8">
+<link rel="match" href="img-with-containment-and-size-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-img-element">
+<style>
+img {
+ contain: paint;
+ width: 200px;
+ height: 100px;
+ will-change: transform;
+}
+</style>
+<script>
+onload = () => {
+ var i = new Image();
+ i.onload = function() {
+ document.body.appendChild(i);
+ document.documentElement.classList.remove("reftest-wait");
+ };
+ i.src = "image.png";
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html
new file mode 100644
index 0000000000..d8d5a84eb7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/img.complete.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML>
+<title>DOM img complete Test</title>
+<meta charset=UTF-8>
+<link rel="author" title="Anselm Hannemann" href="http://anselm-hannemann.com/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img id="imgTestTag">
+<img src="" id="imgTestTag2">
+<img id="imgTestTag3" style="width: 80px; height:auto;">
+<img id="imgTestTag4">
+<img id="imgTestTag5">
+<div id="image-container"></div>
+
+<script>
+ var imageInstance = document.createElement('img');
+ imageInstance.style.display = 'none';
+
+ document.body.appendChild(imageInstance);
+</script>
+
+<div id="log"></div>
+<script>
+ test(function() {
+ assert_true(document.getElementById("imgTestTag").complete);
+ }, "img src and srcset omitted");
+
+ test(function() {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ }, "img src and srcset omitted on newly-created image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ var img = document.createElement("img");
+ cont.appendChild(img);
+ assert_true(img.complete);
+ }, "img src and srcset omitted on newly-created-and-inserted image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ cont.innerHTML = "<img>";
+ assert_true(cont.querySelector("img").complete);
+ }, "img src and srcset omitted on newly-created-via-innerHTML image");
+
+ test(function() {
+ assert_true(document.getElementById("imgTestTag2").complete);
+ }, "img src empty and srcset omitted");
+
+ test(function() {
+ var img = document.createElement("img");
+ img.setAttribute("src", "");
+ assert_true(img.complete);
+ }, "img src empty and srcset omitted on newly-created image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ var img = document.createElement("img");
+ img.setAttribute("src", "");
+ cont.appendChild(img);
+ assert_true(img.complete);
+ }, "img src empty and srcset omitted on newly-created-and-inserted image");
+
+ test(function() {
+ var cont = document.getElementById("image-container");
+ this.add_cleanup(() => { cont.innerHTML = "" });
+ cont.innerHTML = "<img src=''>";
+ assert_true(cont.querySelector("img").complete);
+ }, "img src empty and srcset omitted on newly-created-via-innerHTML image");
+
+ test(function() {
+ var img = document.createElement("img");
+ img.src = location.href;
+ assert_false(img.complete, "Should have a load going");
+ img.removeAttribute("src");
+ assert_true(img.complete);
+ }, "img src and srcset omitted on image after it started a load");
+
+ // test if set to true after img is completely available
+ async_test(t => {
+ var loaded = false;
+ const img = document.getElementById("imgTestTag3");
+ img.onload = t.step_func_done(function(){
+ assert_false(loaded);
+ loaded = true;
+ assert_true(img.complete);
+ var currentSrc = img.currentSrc;
+ var expectedUrl = new URL("3.jpg", window.location);
+ assert_equals(new URL(currentSrc).pathname, expectedUrl.pathname);
+ }, "Only one onload, despite setting the src twice");
+
+ img.src = 'test' + Math.random();
+ //test if img.complete is set to false if src is changed
+ assert_false(img.complete, "src changed, should be set to false")
+ //change src again, should make only one request as per 'await stable state'
+ img.src = '3.jpg?nocache=' + Math.random();
+ }, "async src complete test");
+
+ async_test(t => {
+ var loaded = false;
+ const img = document.getElementById("imgTestTag5")
+ img.onload = t.step_func_done(function(){
+ assert_false(loaded);
+ loaded = true;
+ assert_true(img.complete);
+ }, "Only one onload, despite setting the srcset twice");
+ //Test if src, srcset is omitted
+ assert_true(img.complete)
+ img.srcset = "/images/green-256x256.png 1x";
+ //test if img.complete is set to false if srcset is present
+ assert_false(img.complete, "srcset present, should be set to false");
+ //change src again, should make only one request as per 'await stable state'
+ img.srcset="/images/green-256x256.png 1.6x"
+ }, "async srcset complete test");
+
+ // https://html.spec.whatwg.org/multipage/multipage/embedded-content-1.html#update-the-image-data
+ // says to "await a stable state" before fetching so we use a separate <script>
+ imageInstance.src = 'image-1.jpg?pipe=trickle(d1)&nocache=' + Math.random(); // make sure the image isn't in cache
+</script>
+<script>
+ // test: The final task that is queued by the networking task source once the resource has been fetched has been queued, but has not yet been run, and the img element is not in the broken state
+ test(function() {
+ assert_false(imageInstance.complete, "imageInstance.complete should be false");
+ var startTime = Date.now();
+ while (true) {
+ if (Date.now() - startTime > 2000) {
+ assert_false(imageInstance.complete, "imageInstance.complete should remain false");
+ break;
+ }
+ if (imageInstance.complete === true) {
+ assert_unreached(".complete should not change within a task");
+ }
+ }
+ },
+ 'IDL attribute complete cannot "randomly" change during a task');
+
+ // test if broken img does not pass
+ async_test(t => {
+ const img = document.getElementById("imgTestTag4");
+
+ img.src = 'brokenimg.jpg';
+
+ //test if img.complete is set to false if src is changed
+ assert_false(img.complete, "src changed to broken img, should be set to false");
+
+ img.onload = img.onerror = t.step_func_done(function(event){
+ assert_equals(event.type, "error");
+ assert_true(img.complete);
+ });
+ }, "async src broken test");
+
+ async_test(t => {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ img.src = `3.jpg?nocache=${Math.random()}`;
+ assert_false(img.complete);
+ img.onload = t.step_func_done(() => {
+ assert_true(img.complete);
+ img.removeAttribute("src");
+ assert_true(img.complete, "Should be complete, since we removed the src");
+ });
+ }, "async src removal test");
+
+ async_test(t => {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ img.srcset = `3.jpg?nocache=${Math.random()} 1x`;
+ assert_false(img.complete);
+ img.onload = t.step_func_done(() => {
+ assert_true(img.complete);
+ img.removeAttribute("srcset");
+ assert_true(img.complete, "Should be complete, since we removed the srcset");
+ });
+ }, "async srcset removal test");
+
+ async_test(t => {
+ var preload = document.createElement("img");
+ var url = `3.jpg?nocache=${Math.random()}`;
+ preload.src = url;
+ preload.onload = t.step_func_done(function() {
+ var img = document.createElement("img");
+ assert_true(img.complete);
+ img.src = url;
+ assert_true(img.complete, "Should be complete because we should hit the available image cache");
+ });
+ }, "async src available image lookup test");
+
+ async_test(t => {
+ var img = document.createElement("img");
+ img.src = `3.jpg?nocache=${Math.random()}`;
+ img.onload = t.step_func_done(function() {
+ assert_true(img.complete);
+ img.src = `3.jpg?nocache=${Math.random()}`;
+ assert_false(img.complete, "Should not be complete because we have started a new load");
+ });
+ }, "async pending request test");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html
new file mode 100644
index 0000000000..37ea8ce754
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invalid-src.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Loading a non-parsing URL as an image should silently fail; triggering appropriate events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<img id=brokenurl />
+<img id=emptysrc />
+<script>
+async_test(function(t) {
+ var img = document.getElementById("brokenurl");
+ img.src = "http://[";
+
+ // The errors should be queued in the event loop, so they should only trigger
+ // after this block of code finishes, not during the img.src setter itself
+ img.addEventListener('error', t.step_func(function() {
+ t.step_timeout(t.step_func_done(), 0);
+ }));
+}, 'src="http://["');
+
+async_test(function(t) {
+ var img = document.getElementById("emptysrc");
+ img.src = "";
+
+ // Setting src to empty string triggers only error event.
+ // The errors should be queued in the event loop, so they should only trigger
+ // after this block of code finishes, not during the img.src setter itself
+ img.addEventListener('error', t.step_func(function() {
+ t.step_timeout(t.step_func_done(), 0);
+ }));
+}, 'src=""');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html
new file mode 100644
index 0000000000..35ebbaa11a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/invisible-image.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<head>
+ <title>Test that below-viewport invisible images that are not marked
+ loading=lazy still load, and block the window load event</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="common.js"></script>
+</head>
+
+<body>
+ <div style="height:1000vh;"></div>
+ <img id="visibility_hidden" style="visibility:hidden;" src='resources/image.png?1'>
+ <img id="visibility_hidden_explicit_eager" style="visibility:hidden;" src='resources/image.png?2'
+ loading="eager">
+
+ <img id="display_none" style="display:none;" src='resources/image.png?3'>
+ <img id="display_none_explicit_eager" style="display:none;" src='resources/image.png?4'
+ loading="eager">
+
+ <img id="attribute_hidden" hidden src='resources/image.png?5'>
+ <img id="attribute_hidden_explicit_eager" hidden src='resources/image.png?6'
+ loading="eager">
+
+ <img id="js_display_none" src='resources/image.png?7'>
+ <img id="js_display_none_explicit_eager" src='resources/image.png?8'
+ loading="eager">
+ <script>
+ document.getElementById("js_display_none").style = 'display:none;';
+
+ const visibility_hidden_element = document.getElementById("visibility_hidden");
+ const visibility_hidden_element_explicit_eager =
+ document.getElementById("visibility_hidden_explicit_eager");
+
+ const display_none_element = document.getElementById("display_none");
+ const display_none_element_explicit_eager =
+ document.getElementById("display_none_explicit_eager");
+
+ const attribute_hidden_element = document.getElementById("attribute_hidden");
+ const attribute_hidden_element_explicit_eager =
+ document.getElementById("attribute_hidden_explicit_eager");
+
+ const js_display_none_element = document.getElementById("js_display_none");
+ const js_display_none_element_explicit_eager =
+ document.getElementById("js_display_none_explicit_eager");
+
+ let have_images_loaded = false;
+
+ async_test(t => {
+ let image_fully_loaded_promise = (element) => {
+ return new Promise(resolve => {
+ element.addEventListener("load", t.step_func(resolve));
+ });
+ }
+
+ Promise.all([
+ image_fully_loaded_promise(visibility_hidden_element),
+ image_fully_loaded_promise(visibility_hidden_element_explicit_eager),
+ image_fully_loaded_promise(display_none_element),
+ image_fully_loaded_promise(display_none_element_explicit_eager),
+ image_fully_loaded_promise(attribute_hidden_element),
+ image_fully_loaded_promise(attribute_hidden_element_explicit_eager),
+ image_fully_loaded_promise(js_display_none_element),
+ image_fully_loaded_promise(js_display_none_element_explicit_eager)
+ ]).then(t.step_func(() => {
+ have_images_loaded = true;
+ })).catch(t.unreached_func("All images should load correctly"));
+
+ window.addEventListener("load", t.step_func_done(() => {
+ assert_true(have_images_loaded,
+ "The images should block the window load event.");
+ }));
+
+ }, "Test that below-viewport invisible images that are not marked " +
+ "loading=lazy still load, and block the window load event");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html
new file mode 100644
index 0000000000..bb4c5991c9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-after.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <style>
+ #bg { background-color: lightgray; position: relative; }
+ #target { position: absolute; width: 48px; height: 48px; border: 2px dashed green; pointer-events: none; }
+ .after { top: 246px; left: 246px; }
+ img { margin: 50px; border: 50px solid white; padding: 50px; }
+ </style>
+ </head>
+ <body>
+ <div id="bg">
+ <div id="target" class="after"></div>
+ <a href="/somewhere/">
+ <img src="/images/blue96x96.png" ismap>
+ </a>
+ </div>
+ <h1>Click inside the dashed rectangle</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html
new file mode 100644
index 0000000000..8349b62783
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-before.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <style>
+ #bg { background-color: lightgray; position: relative; }
+ #target { position: absolute; width: 96px; height: 96px; border: 2px dashed green; pointer-events: none; }
+ .before { top: 50px; left: 50px; }
+ img { margin: 50px; border: 50px solid white; padding: 50px; }
+ </style>
+ </head>
+ <body>
+ <div id="bg">
+ <div id="target" class="before"></div>
+ <a href="/somewhere/">
+ <img src="/images/blue96x96.png" ismap>
+ </a>
+ </div>
+ <h1>Click inside the dashed rectangle</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html
new file mode 100644
index 0000000000..fdecee9ace
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-iframe-inside.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <style>
+ #bg { background-color: lightgray; position: relative; }
+ #target { position: absolute; width: 96px; height: 96px; border: 2px dashed green; pointer-events: none; }
+ .in { top: 148px; left: 148px; }
+ img { margin: 50px; border: 50px solid white; padding: 50px; }
+ </style>
+ </head>
+ <body>
+ <div id="bg">
+ <div id="target" class="in"></div>
+ <a href="/common/blank.html">
+ <img src="/images/blue96x96.png" ismap>
+ </a>
+ </div>
+ <h1>Click inside the dashed rectangle</h1>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html
new file mode 100644
index 0000000000..4d77e677e4
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/ismap/img-ismap-coordinates-manual.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+ <head>
+ <title>img ismap attribute coordinate origin</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style>
+ iframe { width: 500px; height: 500px; }
+ </style>
+ </head>
+ <body>
+ <iframe></iframe>
+ <div id="log"></div>
+ <script type="text/javascript">
+ tests = [
+ {
+ file: "img-ismap-coordinates-iframe-inside.html",
+ },
+ {
+ test: async_test("Coordinates within the content box of an image map have origin of the context box"),
+ resultMinXY: 0,
+ resultMaxXY: 96,
+ },
+ {
+ file: "img-ismap-coordinates-iframe-before.html",
+ },
+ {
+ test: async_test("Coordinates within the margin/padding (top-left) of the image map are clamped to zero"),
+ resultMinXY: 0,
+ resultMaxXY: 0,
+ },
+ {
+ file: "img-ismap-coordinates-iframe-after.html",
+ },
+ {
+ test: async_test("Coordinates within the margin/padding (bottom-right) of the image map have origin in the content box"),
+ resultMinXY: 97,
+ resultMaxXY: 146,
+ }
+ ];
+ testIndex = 0;
+
+ var iframe = document.querySelector('iframe');
+ iframe.onload = function testInit() {
+ if (testIndex % 2 == 0) {
+ testIndex++;
+ return;
+ }
+ // User clicked on a results...
+ var url = iframe.contentWindow.location.toString();
+ var test = tests[testIndex].test;
+ var minXY = tests[testIndex].resultMinXY;
+ var maxXY = tests[testIndex].resultMaxXY;
+ testIndex++;
+ if (testIndex < tests.length)
+ iframe.src = tests[testIndex].file; // Advance the test...
+ // Validate the last test's results...
+ test.step(function () {
+ var i = url.indexOf("?");
+ assert_not_equals(i, -1);
+ var coordsStr = url.substr(i+1);
+ var i = coordsStr.indexOf(',');
+ assert_not_equals(i, -1);
+ var x = parseFloat(coordsStr.substring(0, i));
+ var y = parseFloat(coordsStr.substring(i+1));
+ assert_greater_than_equal(x, minXY);
+ assert_less_than_equal(x, maxXY);
+ assert_greater_than_equal(y, minXY);
+ assert_less_than_equal(y, maxXY);
+ test.done();
+ });
+ if (testIndex >= tests.length)
+ iframe.style.display = "none";
+ }
+ iframe.src = tests[0].file;
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html
new file mode 100644
index 0000000000..52e91bc087
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-does-not-coalesce-in-flight-requests.sub.tentative.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+<title>List of available images does not coalesce in-flight requests</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-list-of-available-images">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+const uuid = "{{uuid()}}";
+const path = location.origin + '/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py';
+
+promise_test(async t => {
+ let first_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject(new Error("The img should not fail to load")) };
+ img.src = path + `?increment=${uuid}&pipe=trickle(d1)`;
+ });
+
+ // As of right now, the spec's #updating-the-image-data step 6 [1] does not
+ // place images into the "list of available images" until they are completely
+ // downloaded and the `load` event is fired. The spec also explicitly states
+ // that coalescing in-flight image requests is not what the list of available
+ // images is for. This test asserts this behavior, though since no browsers
+ // follow this behavior (they all allow coalescing in-flight requests for the
+ // same image resource) we've started discussion about this in
+ // https://github.com/whatwg/html/issues/7005. If that issue resolves in
+ // letting in-flight requests into the list of available images, then we
+ // should changes the expectations of this test.
+ //
+ // [1]: https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data
+ let second_image_promise = new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = resolve;
+ img.onerror = e => { reject("The img should not fail to load") };
+ img.src = path + `?increment=${uuid}&pipe=trickle(d1)`;
+ });
+
+ await Promise.all([first_image_promise, second_image_promise]);
+ const response = await fetch(path + `?read=${uuid}`);
+ const request_count = await response.text();
+
+ assert_equals(request_count, "2", "The server should have seen exactly two " +
+ "requests, since the second image request " +
+ "above did not coalesce with the first " +
+ "in-flight one");
+}, 'list of available images does not coalesce in-flight requests');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html
new file mode 100644
index 0000000000..4843d21915
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/list-of-available-images-matching.https.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<html>
+<title>List of available images tuple-matching logic</title>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-list-of-available-images">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<script>
+const path = location.origin + '/html/semantics/embedded-content/the-img-element/';
+const image_url = path + 'image-1.jpg';
+
+function syncWait(ms) {
+ const start = Date.now();
+ while (Date.now() - start < ms);
+}
+
+let sawNoCorsRequest = false;
+
+navigator.serviceWorker.onmessage = ({data}) => {
+ if (data.url === image_url && data.mode === 'no-cors') {
+ sawNoCorsRequest = true;
+ }
+};
+
+promise_test(t => {
+ return service_worker_unregister_and_register(t, 'resources/sw.js', path)
+ .then(r => {
+ return wait_for_state(t, r.installing, 'activated');
+ });
+}, 'registering service worker');
+
+promise_test(async t => {
+ const img = new Image();
+
+ function load_img_promise() {
+ return new Promise((resolve, reject) => {
+ img.onload = resolve;
+ img.onerror = e => { reject("The img should not fail to load") };
+
+ img.src = image_url;
+ // If there is not a matching image in the list of available images, the
+ // actual fetch operation is queued as a microtask, so we will see a
+ // request with mode 'cors' due to setting the crossorigin attribute
+ // below.
+ syncWait(500);
+ img.crossOrigin = 'anonymous';
+ });
+ };
+
+ await load_img_promise();
+ assert_false(sawNoCorsRequest, "The image is not fetched with mode: no-cors");
+ await new Promise(resolve => {
+ img.onload = img.onerror = resolve;
+ img.src = '';
+ img.crossOrigin = null;
+ });
+ await load_img_promise();
+ assert_false(sawNoCorsRequest, "The image is not fetched with mode: no-cors");
+
+}, 'list of available images tuple-matching logic');
+
+promise_test(t => {
+ return service_worker_unregister(t, path);
+}, 'unregistering service worker');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html
new file mode 100644
index 0000000000..3c95fae5bf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/move-element-and-scroll.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load being moved to another document
+ and then scrolled to</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="common.js"></script>
+</head>
+
+<body>
+ <div id="tall_div" style="height:1000vh"></div>
+ <div id="below_viewport_div"></div>
+ <img id="below_viewport" src='resources/image.png?below_viewport' loading="lazy">
+
+ <script>
+ const tall_div = document.getElementById("tall_div");
+ const below_viewport_element = document.getElementById("below_viewport");
+ const below_viewport_div = document.getElementById("below_viewport_div");
+
+ async_test(function(t) {
+ below_viewport_element.onload =
+ t.unreached_func("The below viewport image should not load");
+ t.step_timeout(t.step_func_done(), 1000);
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute("style", "display:none");
+ iframe.srcdoc = "<body></body>";
+ iframe.onload = () => {
+ const adopted_img = iframe.contentDocument.adoptNode(below_viewport_element);
+ iframe.contentDocument.body.appendChild(adopted_img);
+ below_viewport_div.scrollIntoView();
+ };
+ document.body.insertBefore(iframe, tall_div);
+ }, "Test that <img> below viewport is not loaded when moved to another " +
+ "document and then scrolled to");
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html
new file mode 100644
index 0000000000..662dc0804f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>naturalWidth and naturalHeight on HTMLImageElement reflect orientation metadata</title>
+<link rel="author" title="Cameron McCormack" href="mailto:cam@mcc.id.au">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.ignore-orientation { image-orientation: none; }
+</style>
+<body>
+<script>
+async_test(function(t) {
+ let img = document.createElement("img");
+ img.src = "/images/green-100x50.png";
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.naturalWidth, 100);
+ assert_equals(img.naturalHeight, 50);
+ img.remove();
+ });
+ document.body.append(img);
+}, "naturalWidth and naturalHeight return correct values for an image without orientation metadata");
+
+async_test(function(t) {
+ let img = document.createElement("img");
+ img.src = "/images/arrow-oriented-upright.jpg";
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.naturalWidth, 144);
+ assert_equals(img.naturalHeight, 240);
+ img.remove();
+ });
+ document.body.append(img);
+}, "naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata");
+
+async_test(function(t) {
+ let img = document.createElement("img");
+ img.src = "/images/arrow-oriented-upright.jpg";
+ img.className = "ignore-orientation";
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.naturalWidth, 144);
+ assert_equals(img.naturalHeight, 240);
+ img.remove();
+ });
+ document.body.append(img);
+}, "naturalWidth and naturalHeight return re-oriented values for an image with orientation metadata even with image-orientation:none");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html
new file mode 100644
index 0000000000..6072138cb3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/non-active-document.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>img in non-active document should not perform loads</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+
+<!-- Per load the image so that any loads in this test would be cached. -->
+<img src=/images/green-1x1.png>
+
+<!-- tests -->
+<template>
+<img>
+</template>
+
+<script>
+
+onload = function() {
+ async_test(function(t) {
+ var p = new DOMParser();
+ var d = p.parseFromString('<img>', 'text/html');
+ var i = d.querySelector('img');
+ i.onerror = t.unreached_func('got unexpected error event');
+ i.onload = t.unreached_func('got unexpected load event');
+ i.src = '/images/green-1x1.png';
+ // delay to ensure there is no load/error event fired.
+ t.step_timeout(t.step_func_done(), 0);
+ }, "DOMParser");
+
+ async_test(function(t) {
+ var d = document.implementation.createHTMLDocument('');
+ d.body.innerHTML = '<img>';
+ var i = d.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.unreached_func('got unexpected load event');
+ i.src = '/images/green-1x1.png';
+ // delay to ensure there is no load/error event fired.
+ t.step_timeout(t.step_func_done(), 0);
+ }, "createHTMLDocument");
+
+ async_test(function(t) {
+ var template = document.querySelector('template');
+ var i = template.content.querySelector('img');
+ i.onerror = this.unreached_func('got unexpected error event');
+ i.onload = this.unreached_func('got unexpected load event');
+ i.src = '/images/green-1x1.png';
+ // delay to ensure there is no load/error event fired.
+ t.step_timeout(t.step_func_done(), 0);
+ }, "<template>");
+};
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html
new file mode 100644
index 0000000000..f58569ede0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/nonexistent-image.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Loading an nonexisting image should fail; triggering appropriate events</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img>
+
+<script>
+ async_test(function(t) {
+ var img = document.querySelector("img");
+ img.onload = this.step_func_done(function() {
+ assert_unreached("image.onload() was not supposed to be called");
+ });
+ img.onerror = this.step_func_done(function(e) {
+ assert_equals(e.type, "error", "image.onerror() called");
+ t.done();
+ });
+ img.src = "404";
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html
new file mode 100644
index 0000000000..401771565a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-below-viewport-image-loading-lazy.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<head>
+ <title>Below-viewport loading=lazy not-rendered images should never load,
+ even when scrolled into view</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- These images must not attempt to load when scrolled into the
+ viewport -->
+ <img id="display_none" style="display:none;" src="resources/image.png?not-rendered-2" loading="lazy"
+ onload="display_none_img.resolve();" onerror="display_none_img.reject();">
+ <img id="attribute_hidden" hidden src="resources/image.png?not-rendered-3" loading="lazy"
+ onload="attribute_hidden_img.resolve();" onerror="attribute_hidden_img.reject();">
+ <img id="js_display_none" src="resources/image.png?not-rendered-4" loading="lazy"
+ onload="js_display_none_img.resolve();" onerror="js_display_none_img.reject();">
+ <script>
+ document.getElementById("js_display_none").style = 'display:none;';
+ </script>
+
+ <!-- Later in the test we'll scroll to this div, instead of the above images,
+ since due to them not being rendered, they cannot be scrolled to -->
+ <div id="rendered_div"></div>
+</body>
+
+<script>
+ const display_none_img = new ElementLoadPromise("display_none");
+ const attribute_hidden_img = new ElementLoadPromise("attribute_hidden");
+ const js_display_none_img = new ElementLoadPromise("js_display_none");
+ const rendered_div_element = document.querySelector('#rendered_div');
+
+ async_test(t => {
+ window.addEventListener("load", t.step_func(() => {
+ rendered_div.scrollIntoView();
+ }));
+
+ const unreached_not_rendered_img_func =
+ t.unreached_func("The not-rendered below-viewport loading=lazy images " +
+ "should not attempt to load.");
+
+ display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ attribute_hidden_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ js_display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ // If none of the above images load after being scrolled to within the below
+ // timeout, the test passes.
+ t.step_timeout(t.done, 2000);
+ }, "Below-viewport loading=lazy not-rendered images should never load, " +
+ "even when scrolled into view");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html
new file mode 100644
index 0000000000..4d929fd8b1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-dimension-getter.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Image intrinsic dimensions are returned if the image isn't rendered</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-img-width">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="container" style="display: none">
+</div>
+<script>
+async_test(function(t) {
+ var img = document.createElement('img');
+ img.onload = t.step_func_done(function() {
+ assert_equals(img.width, 389, "intrinsic width should've been returned")
+ assert_equals(img.height, 590, "intrinsic height should've been returned")
+ document.getElementById('container').appendChild(img);
+ assert_equals(img.width, 389, "intrinsic width should've been returned");
+ assert_equals(img.height, 590, "intrinsic height should've been returned");
+ });
+ img.src = "image-1.jpg";
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html
new file mode 100644
index 0000000000..25aaedb2b2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/not-rendered-image-loading-lazy.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<head>
+ <title>In-viewport loading=lazy not-rendered images should never load</title>
+ <link rel="author" title="Rob Buis" href="mailto:rbuis@igalia.com">
+ <link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<body>
+ <!-- These images must not attempt to load -->
+ <img id="display_none" style="display:none;" src="resources/image.png?not-rendered-image-loading-lazy-2" loading="lazy"
+ onload="display_none_img.resolve();" onerror="display_none_img.reject();">
+ <img id="attribute_hidden" hidden src="resources/image.png?not-rendered-image-loading-lazy-3" loading="lazy"
+ onload="attribute_hidden_img.resolve();" onerror="attribute_hidden_img.reject();">
+ <img id="js_display_none" src="resources/image.png?not-rendered-image-loading-lazy-4" loading="lazy"
+ onload="js_display_none_img.resolve();" onerror="js_display_none_img.reject();">
+ <script>
+ document.getElementById("js_display_none").style = 'display:none;';
+ </script>
+</body>
+
+<script>
+ const display_none_img = new ElementLoadPromise("display_none");
+ const attribute_hidden_img = new ElementLoadPromise("attribute_hidden");
+ const js_display_none_img = new ElementLoadPromise("js_display_none");
+
+ async_test(t => {
+ const unreached_not_rendered_img_func =
+ t.unreached_func("The not-rendered in-viewport loading=lazy images " +
+ "should not attempt to load.");
+
+ display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ attribute_hidden_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ js_display_none_img.promise
+ .then(unreached_not_rendered_img_func)
+ .catch(unreached_not_rendered_img_func);
+
+ t.step_timeout(t.done, 2000);
+ }, "In-viewport loading=lazy not-rendered images should never load");
+</script>
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html
new file mode 100644
index 0000000000..8999276503
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/null-image-source.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Null image source check for src, srcset and picture parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<img id="src_id" src="">
+<img id="srcset_id" srcset="">
+<picture><img id="parent_picture_id"></picture>
+<script>
+async_test(function(t) {
+ img = document.getElementById('src_id');
+ img.onerror = t.step_func(function(e) {
+ assert_equals(e.type, "error", "null image source check failed");
+ t.done();
+ });
+}, "img with empty src");
+
+async_test(function(t) {
+ img = document.getElementById('srcset_id');
+ img.onerror = t.unreached_func("empty srcset fires an error");
+ t.step_timeout(function() { t.done(); }, 2000);
+}, "img with empty srcset");
+
+async_test(function(t) {
+ img = document.getElementById('parent_picture_id');
+ img.onerror = t.unreached_func("null img with picture parent fires an error");
+ t.step_timeout(function() { t.done(); }, 2000);
+}, "img with picture parent");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html
new file mode 100644
index 0000000000..08c01616bd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/picture-loading-lazy.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' in picture elements load when near the viewport</title>
+ <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+ <link rel="help" href="https://github.com/scott-little/lazyload">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="../resources/common.js"></script>
+</head>
+
+<script>
+const in_viewport_img = new ElementLoadPromise("in_viewport_img");
+const lazy_attribute_img = new ElementLoadPromise("lazy_attribute_img");
+const eager_attribute_img = new ElementLoadPromise("eager_attribute_img");
+
+const document_load_promise = new Promise(resolve => {
+ window.addEventListener("load", resolve);
+});
+
+async_test(function(t) {
+ document_load_promise.then(t.step_func_done(function() {
+ assert_false(lazy_attribute_img.element().complete);
+ lazy_attribute_img.element().scrollIntoView();
+ }));
+}, "Test that the loading=lazy <picture> element below viewport was deferred, on document load.");
+
+async_test(function(t) {
+ in_viewport_img.promise.then(t.step_func_done());
+}, "Test that in viewport <picture> element was loaded");
+
+async_test(function(t) {
+ eager_attribute_img.promise.then(t.step_func_done());
+}, "Test that eager <picture> element was loaded");
+
+async_test(function(t) {
+ lazy_attribute_img.promise.then(t.step_func_done());
+}, "Test that deferred <picture> element was loaded-in as well, after scrolled down");
+
+</script>
+
+<body>
+<picture>
+ <source sizes='50vw' srcset='resources/image.png?in_viewport_img'>
+ <img id='in_viewport_img' src='img-not-loaded.png' loading="lazy" onload="in_viewport_img.resolve();">
+</picture>
+<div style="height:10000px;"></div>
+<picture>
+ <source sizes='50vw' srcset='resources/image.png?lazy_attribute_img'>
+ <img id='lazy_attribute_img' src='img-not-loaded.png' loading="lazy" onload="lazy_attribute_img.resolve();">
+</picture>
+<picture>
+ <source sizes='50vw' srcset='resources/image.png?eager_attribute_img'>
+ <img id='eager_attribute_img' src='img-not-loaded.png' loading="eager" onload="eager_attribute_img.resolve();">
+</picture>
+
+<!--
+ This async script loads very slowly in order to ensure that, if the
+ below_viewport image has started loading, it has a chance to finish
+ loading before window.load() happens, so that the test will dependably fail
+ in that case instead of potentially passing depending on how long different
+ resource fetches take.
+-->
+<script async src="/common/slow.py"></script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html
new file mode 100644
index 0000000000..d3784671b8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations-lazy.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<title>img relevant mutations, lazy-loaded</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/relevant-mutations.js"></script>
+<div id=log></div>
+
+<img src="/images/green-2x2.png"> <!-- block the window load event -->
+
+<!-- should invoke update the image data, but omit events for layout changes -->
+<!-- but also see https://github.com/whatwg/html/issues/8492 -->
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" width="2" loading="lazy" data-desc="width attribute changes">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="width property changes">
+
+<div style="width: 2px">
+ <img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 100%" loading="lazy" data-desc="percentage width, CB width changes">
+</div>
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="height: 2px; aspect-ratio: 2 / 2" loading="lazy" data-desc="height property changes (with aspect-ratio)">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="being rendered changes">
+
+<!-- should not invoke update the image data -->
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="loading attribute state changes">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="display property changes to inline-block">
+
+<img srcset="/images/green-2x2.png 2w" sizes="auto" style="width: 2px" loading="lazy" data-desc="loading attribute changes to LAZY">
+
+<script>
+const rAF = () => new Promise(resolve => requestAnimationFrame(resolve));
+
+onload = async function() {
+
+ await rAF();
+ await rAF();
+
+ // lazy-loaded images should have fired their first 'load' event at this point.
+
+ t('width attribute changes', function(img) {
+ img.width = '4';
+ }, 'load');
+
+ t('width property changes', function(img) {
+ img.style.width = '4px';
+ }, 'timeout');
+
+ t('percentage width, CB width changes', function(img) {
+ img.parentNode.style.width = '4px';
+ }, 'timeout');
+
+ t('height property changes (with aspect-ratio)', function(img) {
+ img.style.height = '4px';
+ }, 'timeout');
+
+ t('loading attribute state changes', function(img) {
+ img.loading = 'eager';
+ }, 'timeout');
+
+ t('being rendered changes', function(img) {
+ img.style.display = 'none';
+ }, 'timeout');
+
+ t('display property changes to inline-block', function(img) {
+ img.style.display = 'inline-block';
+ }, 'timeout');
+
+ t('loading attribute changes to LAZY', function(img) {
+ img.setAttribute('loading', 'LAZY');
+ }, 'timeout');
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html
new file mode 100644
index 0000000000..24e15543cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/relevant-mutations.html
@@ -0,0 +1,628 @@
+<!doctype html>
+<title>img relevant mutations</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/relevant-mutations.js"></script>
+<div id=log></div>
+
+<!-- should invoke update the image data -->
+
+<img data-desc="src set">
+<img src="/images/green-2x2.png" data-desc="src changed">
+<img src="/images/green-2x2.png" data-desc="src removed">
+
+<img data-desc="srcset set">
+<img srcset="/images/green-2x2.png" data-desc="srcset changed">
+<img srcset="/images/green-2x2.png" data-desc="srcset removed">
+
+<img data-desc="sizes set">
+<img sizes="" data-desc="sizes changed">
+<img sizes="" data-desc="sizes removed">
+
+<img src="/images/green-2x2.png" data-desc="src set to same value">
+
+<img data-desc="crossorigin absent to empty, src absent">
+<img data-desc="crossorigin absent to anonymous, src absent">
+<img data-desc="crossorigin absent to use-credentials, src absent">
+<img crossorigin data-desc="crossorigin empty to absent, src absent">
+<img crossorigin data-desc="crossorigin empty to use-credentials, src absent">
+<img crossorigin=anonymous data-desc="crossorigin anonymous to absent, src absent">
+<img crossorigin=anonymous data-desc="crossorigin anonymous to use-credentials, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to absent, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to empty, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to anonymous, src absent">
+<img crossorigin=use-credentials data-desc="crossorigin use-credentials to invalid, src absent">
+
+<img src="/images/green-2x2.png" data-desc="crossorigin absent to empty, src already set">
+<img src="/images/green-2x2.png" data-desc="crossorigin absent to anonymous, src already set">
+<img src="/images/green-2x2.png" data-desc="crossorigin absent to use-credentials, src already set">
+<img src="/images/green-2x2.png" crossorigin data-desc="crossorigin empty to absent, src already set">
+<img src="/images/green-2x2.png" crossorigin data-desc="crossorigin empty to use-credentials, src already set">
+<img src="/images/green-2x2.png" crossorigin=anonymous data-desc="crossorigin anonymous to absent, src already set">
+<img src="/images/green-2x2.png" crossorigin=anonymous data-desc="crossorigin anonymous to use-credentials, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to absent, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to empty, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to anonymous, src already set">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin use-credentials to invalid, src already set">
+
+<img data-desc="referrerpolicy absent to no-referrer-when-downgrade, src absent">
+<img data-desc="referrerpolicy absent to no-referrer, src absent">
+<img referrerpolicy data-desc="referrerpolicy empty to no-referrer-when-downgrade, src absent">
+<img referrerpolicy data-desc="referrerpolicy empty to no-referrer, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to absent, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to empty, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to no-referrer, src absent">
+<img referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to invalid, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to absent, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to empty, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to no-referrer-when-downgrade, src absent">
+<img referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to invalid, src absent">
+
+<img src="/images/green-2x2.png" data-desc="referrerpolicy absent to no-referrer-when-downgrade, src already set">
+<img src="/images/green-2x2.png" data-desc="referrerpolicy absent to no-referrer, src already set">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy empty to no-referrer-when-downgrade, src already set">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy empty to no-referrer, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to absent, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to empty, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to no-referrer, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer-when-downgrade data-desc="referrerpolicy no-referrer-when-downgrade to invalid, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to absent, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to empty, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to no-referrer-when-downgrade, src already set">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy no-referrer to invalid, src already set">
+
+<img src="/images/green-2x2.png" data-desc="inserted into picture"><picture></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="removed from picture"></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, previous source inserted"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has srcset set"></picture>
+<picture><source srcset=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has srcset changed"></picture>
+<picture><source srcset=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has srcset removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has sizes set"></picture>
+<picture><source sizes=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has sizes changed"></picture>
+<picture><source sizes=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has sizes removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has media set"></picture>
+<picture><source media=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has media changed"></picture>
+<picture><source media=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has media removed"></picture>
+
+<picture><source><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has type set"></picture>
+<picture><source type=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has type changed"></picture>
+<picture><source type=""><img src="/images/green-2x2.png" data-desc="parent is picture, previous source has type removed"></picture>
+
+<img srcset="/images/green-2x2.png" data-desc="srcset is set to same value">
+<img srcset="/images/green-2x2.png" sizes data-desc="sizes is set to same value">
+
+<img src="/images/green-2x2.png" data-desc="crossorigin state not changed: absent, removeAttribute">
+<img src="/images/green-2x2.png" crossorigin data-desc="crossorigin state not changed: empty to anonymous">
+<img src="/images/green-2x2.png" crossorigin=anonymous data-desc="crossorigin state not changed: anonymous to foobar">
+<img src="/images/green-2x2.png" crossorigin=use-credentials data-desc="crossorigin state not changed: use-credentials to USE-CREDENTIALS">
+
+<img src="/images/green-2x2.png" data-desc="referrerpolicy state not changed: absent, removeAttribute">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy state not changed: empty to empty">
+<img src="/images/green-2x2.png" referrerpolicy data-desc="referrerpolicy state not changed: empty to invalid">
+<img src="/images/green-2x2.png" data-desc="referrerpolicy state not changed: absent to invalid">
+<img src="/images/green-2x2.png" referrerpolicy=no-referrer data-desc="referrerpolicy state not changed: no-referrer to NO-REFERRER">
+<img src="/images/green-2x2.png" referrerpolicy=foobar data-desc="referrerpolicy state not changed: invalid to other-invalid">
+
+<img src="/images/green-2x2.png" data-desc="inserted into picture ancestor"><picture><span></span></picture>
+<picture><span><img src="/images/green-2x2.png" data-desc="removed from picture ancestor"></span></picture>
+
+<picture><span><img src="/images/green-2x2.png" data-desc="ancestor picture has a source inserted"></span></picture>
+<picture><source><span><img src="/images/green-2x2.png" data-desc="ancestor picture has a source removed"></span></picture>
+
+<picture><span><img src="/images/green-2x2.png" data-desc="ancestor picture; previous sibling source inserted"></span></picture>
+<picture><span><source><img src="/images/green-2x2.png" data-desc="ancestor picture; previous sibling source removed"></span></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following sibling source inserted"></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following sibling source removed"><source></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following sibling source has srcset set"><source></picture>
+
+<img src="/images/green-2x2.png" data-desc="media on img set">
+<img src="/images/green-2x2.png" data-desc="type on img set">
+<img src="/images/green-2x2.png" data-desc="class on img set">
+<img src="/images/green-2x2.png" data-desc="alt on img set">
+
+<picture><source><img src="/images/green-2x2.png" data-desc="src on previous sibling source set"></picture>
+<picture><source><img src="/images/green-2x2.png" data-desc="class on previous sibling source set"></picture>
+
+<img src="/images/green-2x2.png" data-desc="inserted/removed children of img">
+
+<picture><img src="/images/green-2x2.png" data-desc="picture is inserted; img has src"></picture><span></span>
+<picture><img srcset="/images/green-2x2.png" data-desc="picture is inserted; img has srcset"></picture><span></span>
+<picture><source srcset="/images/green-2x2.png"><img src="/images/green-2x2.png" data-desc="picture is inserted; img has previous sibling source"></picture><span></span>
+<picture><img src="/images/green-2x2.png" data-desc="picture is inserted; img has following sibling source"><source srcset="/images/green-2x2.png"></picture><span></span>
+
+<picture><img src="/images/green-2x2.png" data-desc="picture is removed; img has src"></picture>
+<picture><img srcset="/images/green-2x2.png" data-desc="picture is removed; img has srcset"></picture>
+<picture><source srcset="/images/green-2x2.png"><img src="/images/green-2x2.png" data-desc="picture is removed; img has previous sibling source"></picture>
+<picture><img src="/images/green-2x2.png" data-desc="picture is removed; img has following sibling source"><source srcset="/images/green-2x2.png"></picture>
+
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img inserted"></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img removed"><img></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img has src set"><img></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img has srcset set"><img></picture>
+<picture><img src="/images/green-2x2.png" data-desc="parent is picture, following img has sizes set"><img></picture>
+
+
+<script>
+onload = function() {
+
+ t('src set', function(img) {
+ img.src = '/images/green-2x2.png';
+ }, 'load');
+
+ t('src changed', function(img) {
+ img.src = '/images/green-2x2.png ';
+ }, 'load');
+
+ t('src removed', function(img) {
+ img.removeAttribute('src');
+ }, 'timeout');
+
+ t('srcset set', function(img) {
+ img.srcset = '/images/green-2x2.png';
+ }, 'load');
+
+ t('srcset changed', function(img) {
+ img.srcset = '/images/green-2x2.png ';
+ }, 'load');
+
+ t('srcset removed', function(img) {
+ img.removeAttribute('srcset');
+ }, 'timeout');
+
+ t('sizes set', function(img) {
+ img.sizes = '';
+ }, 'timeout');
+
+ t('sizes changed', function(img) {
+ img.sizes = ' ';
+ }, 'timeout');
+
+ t('sizes removed', function(img) {
+ img.removeAttribute('sizes');
+ }, 'timeout');
+
+ t('src set to same value', function(img) {
+ img.src = '/images/green-2x2.png';
+ }, 'load');
+
+ // When src is absent, changing the crossorigin attribute state MUST NOT
+ // generate events.
+
+ t('crossorigin absent to empty, src absent', function(img) {
+ img.crossOrigin = '';
+ }, 'timeout');
+
+ t('crossorigin absent to anonymous, src absent', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'timeout');
+
+ t('crossorigin absent to use-credentials, src absent', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'timeout');
+
+ t('crossorigin empty to absent, src absent', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin empty to use-credentials, src absent', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'timeout');
+
+ t('crossorigin anonymous to absent, src absent', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin anonymous to use-credentials, src absent', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'timeout');
+
+ t('crossorigin use-credentials to absent, src absent', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin use-credentials to empty, src absent', function(img) {
+ img.crossOrigin = '';
+ }, 'timeout');
+
+ t('crossorigin use-credentials to anonymous, src absent', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'timeout');
+
+ t('crossorigin use-credentials to invalid, src absent', function(img) {
+ img.crossOrigin = 'foobar';
+ }, 'timeout');
+
+ // When src is set, changing the crossorigin attribute state MUST generate
+ // events.
+
+ t('crossorigin absent to empty, src already set', function(img) {
+ img.crossOrigin = '';
+ }, 'load');
+
+ t('crossorigin absent to anonymous, src already set', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'load');
+
+ t('crossorigin absent to use-credentials, src already set', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'load');
+
+ t('crossorigin empty to absent, src already set', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'load');
+
+ t('crossorigin empty to use-credentials, src already set', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'load');
+
+ t('crossorigin anonymous to absent, src already set', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'load');
+
+ t('crossorigin anonymous to use-credentials, src already set', function(img) {
+ img.crossOrigin = 'use-credentials';
+ }, 'load');
+
+ t('crossorigin use-credentials to absent, src already set', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'load');
+
+ t('crossorigin use-credentials to empty, src already set', function(img) {
+ img.crossOrigin = '';
+ }, 'load');
+
+ t('crossorigin use-credentials to anonymous, src already set', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'load');
+
+ t('crossorigin use-credentials to invalid, src already set', function(img) {
+ img.crossOrigin = 'foobar';
+ }, 'load');
+
+ // When src is absent, changing the referrerpolicy attribute state MUST NOT
+ // generate events.
+
+ t('referrerpolicy absent to no-referrer-when-downgrade, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'timeout');
+
+ t('referrerpolicy absent to no-referrer, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'timeout');
+
+ t('referrerpolicy empty to no-referrer-when-downgrade, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'timeout');
+
+ t('referrerpolicy empty to no-referrer, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to absent, src absent', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to empty, src absent', function(img) {
+ img.referrerPolicy = '';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to no-referrer, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer-when-downgrade to invalid, src absent', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to absent, src absent', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to empty, src absent', function(img) {
+ img.referrerPolicy = '';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to no-referrer-when-downgrade, src absent', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'timeout');
+
+ t('referrerpolicy no-referrer to invalid, src absent', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ // When src is set, changing the referrerpolicy attribute state MUST generate
+ // events.
+
+ t('referrerpolicy absent to no-referrer-when-downgrade, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'load');
+
+ t('referrerpolicy absent to no-referrer, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'load');
+
+ t('referrerpolicy empty to no-referrer-when-downgrade, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'load');
+
+ t('referrerpolicy empty to no-referrer, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to absent, src already set', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to empty, src already set', function(img) {
+ img.referrerPolicy = '';
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to no-referrer, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer';
+ }, 'load');
+
+ t('referrerpolicy no-referrer-when-downgrade to invalid, src already set', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'load');
+
+ t('referrerpolicy no-referrer to absent, src already set', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'load');
+
+ t('referrerpolicy no-referrer to empty, src already set', function(img) {
+ img.referrerPolicy = '';
+ }, 'load');
+
+ t('referrerpolicy no-referrer to no-referrer-when-downgrade, src already set', function(img) {
+ img.referrerPolicy = 'no-referrer-when-downgrade';
+ }, 'load');
+
+ t('referrerpolicy no-referrer to invalid, src already set', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'load');
+
+
+ t('inserted into picture', function(img) {
+ img.nextSibling.appendChild(img);
+ }, 'load');
+
+ t('removed from picture', function(img) {
+ img.parentNode.removeChild(img);
+ }, 'load');
+
+ t('parent is picture, previous source inserted', function(img) {
+ img.parentNode.insertBefore(document.createElement('source'), img);
+ }, 'load');
+
+ t('parent is picture, previous source removed', function(img) {
+ img.parentNode.removeChild(img.previousSibling);
+ }, 'load');
+
+ t('parent is picture, previous source has srcset set', function(img) {
+ img.previousSibling.srcset = '';
+ }, 'load');
+
+ t('parent is picture, previous source has srcset changed', function(img) {
+ img.previousSibling.srcset = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has srcset removed', function(img) {
+ img.previousSibling.removeAttribute('srcset');
+ }, 'load');
+
+ t('parent is picture, previous source has sizes set', function(img) {
+ img.previousSibling.sizes = '';
+ }, 'load');
+
+ t('parent is picture, previous source has sizes changed', function(img) {
+ img.previousSibling.sizes = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has sizes removed', function(img) {
+ img.previousSibling.removeAttribute('sizes');
+ }, 'load');
+
+ t('parent is picture, previous source has media set', function(img) {
+ img.previousSibling.media = '';
+ }, 'load');
+
+ t('parent is picture, previous source has media changed', function(img) {
+ img.previousSibling.media = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has media removed', function(img) {
+ img.previousSibling.removeAttribute('media');
+ }, 'load');
+
+ t('parent is picture, previous source has type set', function(img) {
+ img.previousSibling.type = '';
+ }, 'load');
+
+ t('parent is picture, previous source has type changed', function(img) {
+ img.previousSibling.type = ' ';
+ }, 'load');
+
+ t('parent is picture, previous source has type removed', function(img) {
+ img.previousSibling.removeAttribute('type');
+ }, 'load');
+
+ t('srcset is set to same value', function(img) {
+ img.srcset = "/images/green-2x2.png";
+ }, 'load');
+
+ t('sizes is set to same value', function(img) {
+ img.sizes = '';
+ }, 'load');
+
+ t('crossorigin state not changed: absent, removeAttribute', function(img) {
+ img.removeAttribute('crossorigin');
+ }, 'timeout');
+
+ t('crossorigin state not changed: empty to anonymous', function(img) {
+ img.crossOrigin = 'anonymous';
+ }, 'timeout');
+
+ t('crossorigin state not changed: anonymous to foobar', function(img) {
+ img.crossOrigin = 'foobar';
+ }, 'timeout');
+
+ t('crossorigin state not changed: use-credentials to USE-CREDENTIALS', function(img) {
+ img.crossOrigin = 'USE-CREDENTIALS';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: absent, removeAttribute', function(img) {
+ img.removeAttribute('referrerpolicy');
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: empty to empty', function(img) {
+ img.referrerPolicy = '';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: empty to invalid', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: absent to invalid', function(img) {
+ img.referrerPolicy = 'foobar';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: no-referrer to NO-REFERRER', function(img) {
+ img.referrerPolicy = 'NO-REFERRER';
+ }, 'timeout');
+
+ t('referrerpolicy state not changed: invalid to other-invalid', function(img) {
+ img.referrerPolicy = 'foobar2';
+ }, 'timeout');
+
+ t('inserted into picture ancestor', function(img) {
+ img.nextSibling.firstChild.appendChild(img);
+ }, 'timeout');
+
+ t('removed from picture ancestor', function(img) {
+ img.parentNode.removeChild(img);
+ }, 'timeout');
+
+ t('ancestor picture has a source inserted', function(img) {
+ img.parentNode.parentNode.insertBefore(document.createElement('source'), img.parentNode);
+ }, 'timeout');
+
+ t('ancestor picture has a source removed', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode.previousSibling);
+ }, 'timeout');
+
+ t('ancestor picture; previous sibling source inserted', function(img) {
+ img.parentNode.insertBefore(document.createElement('source'), img);
+ }, 'timeout');
+
+ t('ancestor picture; previous sibling source removed', function(img) {
+ img.parentNode.removeChild(img.previousSibling);
+ }, 'timeout');
+
+ t('parent is picture, following sibling source inserted', function(img) {
+ img.parentNode.appendChild(document.createElement('source'));
+ }, 'timeout');
+
+ t('parent is picture, following sibling source removed', function(img) {
+ img.parentNode.removeChild(img.nextSibling);
+ }, 'timeout');
+
+ t('parent is picture, following sibling source has srcset set', function(img) {
+ img.nextSibling.srcset = '';
+ }, 'timeout');
+
+ t('media on img set', function(img) {
+ img.setAttribute('media', '');
+ }, 'timeout');
+
+ t('type on img set', function(img) {
+ img.setAttribute('type', '');
+ }, 'timeout');
+
+ t('class on img set', function(img) {
+ img.className = '';
+ }, 'timeout');
+
+ t('alt on img set', function(img) {
+ img.alt = '';
+ }, 'timeout');
+
+ t('src on previous sibling source set', function(img) {
+ img.previousSibling.setAttribute('src', '');
+ }, 'timeout');
+
+ t('class on previous sibling source set', function(img) {
+ img.previousSibling.className = '';
+ }, 'timeout');
+
+ t('inserted/removed children of img', function(img) {
+ img.appendChild(document.createElement('source'));
+ setTimeout(this.step_func(function() {
+ img.removeChild(img.firstChild);
+ }), 0);
+ }, 'timeout');
+
+ t('picture is inserted; img has src', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is inserted; img has srcset', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is inserted; img has previous sibling source', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is inserted; img has following sibling source', function(img) {
+ img.parentNode.nextSibling.appendChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has src', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has srcset', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has previous sibling source', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('picture is removed; img has following sibling source', function(img) {
+ img.parentNode.parentNode.removeChild(img.parentNode);
+ }, 'timeout');
+
+ t('parent is picture, following img inserted', function(img) {
+ img.parentNode.appendChild(document.createElement('img'));
+ }, 'timeout');
+
+ t('parent is picture, following img removed', function(img) {
+ img.parentNode.removeChild(img.nextSibling);
+ }, 'timeout');
+
+ t('parent is picture, following img has src set', function(img) {
+ img.nextSibling.src = '';
+ }, 'timeout');
+
+ t('parent is picture, following img has srcset set', function(img) {
+ img.nextSibling.srcset = '';
+ }, 'timeout');
+
+ t('parent is picture, following img has sizes set', function(img) {
+ img.nextSibling.sizes = '';
+ }, 'timeout');
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html
new file mode 100644
index 0000000000..8e7fa1bfbf
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/remove-element-and-scroll.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+ <title>Images with loading='lazy' load being removed and then scrolled to</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="common.js"></script>
+</head>
+
+<body>
+ <img id="in_viewport" src='resources/image.png?in_viewport&pipe=trickle(d1)'>
+ <div style="height:1000vh"></div>
+ <div id="below_viewport_div"></div>
+ <img id="below_viewport" src='resources/image.png?below_viewport' loading="lazy">
+
+ <script>
+ const in_viewport_element = document.getElementById("in_viewport");
+ const below_viewport_element = document.getElementById("below_viewport");
+ const below_viewport_div = document.getElementById("below_viewport_div");
+
+ async_test(t => {
+ below_viewport_element.onload = t.unreached_func("Removed loading=lazy image " +
+ "should not load when its old position is scrolled to.");
+ below_viewport_element.remove();
+
+ in_viewport_element.onload = () => {
+ below_viewport_div.scrollIntoView();
+ t.step_timeout(t.step_func_done(), 2000);
+ };
+ }, "Test that <img> below viewport is not loaded when removed from the " +
+ "document and then scrolled to");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.png
new file mode 100644
index 0000000000..62949e08d8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/blue-10.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpg
new file mode 100644
index 0000000000..a4f14f54d6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/cat.jpg
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.png
new file mode 100644
index 0000000000..25b76c3c6f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/green.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py
new file mode 100644
index 0000000000..d16a3e591d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-and-stash.py
@@ -0,0 +1,44 @@
+# This is a simple implementation of a server-side stash that supports two
+# operations:
+# - increment:
+# Increments a value in the stash keyed by a given uuid, and returns an
+# image file
+# - read:
+# Returns the value in the stash keyed by a given uuid or 0 otherwise.
+# This is a read-only operation, and does not remove the value from the
+# stash as-is the default WPT stash behavior:
+# https://web-platform-tests.org/tools/wptserve/docs/stash.html.
+
+import os
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ if b"increment" in request.GET:
+ uuid = request.GET[b"increment"]
+
+ # First, increment the stash value keyed by `uuid`, and write it back to the
+ # stash. Writing it back to the stash is necessary since `take()` actually
+ # removes the value whereas we want to increment it.
+ stash_value = request.server.stash.take(uuid)
+ if stash_value is None:
+ stash_value = 0
+ request.server.stash.put(uuid, stash_value + 1)
+
+ # Return a basic image.
+ response_headers = [(b"Content-Type", b"image/png")]
+ image_path = os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"image.png")
+ return (200, response_headers, open(image_path, mode='rb').read())
+
+ elif b"read" in request.GET:
+ uuid = request.GET[b"read"]
+ stash_value = request.server.stash.take(uuid)
+
+ if stash_value is None:
+ stash_value = 0
+ # Write the stash value keyed by `uuid` back to the stash. This is necessary
+ # because `take()` actually removes it, but we want a read-only read.
+ request.server.stash.put(uuid, stash_value);
+ return (200, [], str(stash_value))
+
+ return (404 , [], "Not found")
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html
new file mode 100644
index 0000000000..f25bd6f4d0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-below-viewport.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<div style="height:1000vh;"></div>
+
+<img id="img" loading="lazy" src="image.png?lazy-below-viewport">
+
+<script>
+ const img = document.querySelector('#img');
+
+ img.addEventListener("load", () => {
+ parent.postMessage("image_loaded", "*");
+ });
+
+ window.addEventListener("load", () => {
+ parent.postMessage("window_loaded", "*");
+ });
+
+ window.addEventListener("message", event => {
+ if (event.data == "scroll") {
+ img.scrollIntoView();
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html
new file mode 100644
index 0000000000..bafdacc883
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image-loading-lazy-in-viewport.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+
+<!-- This frame is used by image-loading-lazy-in-cross-origin-iframe-002.sub.html -->
+
+<img id="img" loading="lazy">
+
+<script>
+ const img = document.querySelector('#img');
+
+ // Prevent the list of available images check from loading the image non-lazily.
+ img.src = "image.png?image-loading-lazy-in-viewport-" + Math.random() + "-" + Date.now();
+
+ img.addEventListener("load", () => {
+ parent.postMessage("image_loaded", "*");
+ });
+
+ window.addEventListener("load", () => {
+ parent.postMessage("window_loaded", "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.png
new file mode 100644
index 0000000000..b712825093
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/image.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html
new file mode 100644
index 0000000000..735b8f6213
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/newwindow.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="height:1000vh;"></div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.png
new file mode 100644
index 0000000000..57bf3ddc52
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/red.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py
new file mode 100644
index 0000000000..bb2071cb97
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/referrer-checker-img.py
@@ -0,0 +1,14 @@
+import os
+
+from wptserve.utils import isomorphic_decode
+
+# Returns a valid image response when request's |referrer| matches
+# |expected_referrer|.
+def main(request, response):
+ referrer = request.headers.get(b"referer", b"")
+ expected_referrer = request.GET.first(b"expected_referrer", b"")
+ response_headers = [(b"Content-Type", b"image/png")]
+ if referrer == expected_referrer:
+ image_path = os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"image.png")
+ return (200, response_headers, open(image_path, mode='rb').read())
+ return (404, response_headers, u"Not found")
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js
new file mode 100644
index 0000000000..8bd079f790
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js
@@ -0,0 +1,20 @@
+addEventListener('install', (event) => {
+ skipWaiting();
+});
+
+addEventListener('activate', (event) => {
+ event.waitUntil(clients.claim());
+});
+
+async function broadcast(msg) {
+ const allClients = await clients.matchAll();
+ for (const client of allClients) {
+ client.postMessage(msg);
+ }
+}
+
+addEventListener('fetch', (event) => {
+ event.waitUntil(
+ broadcast({ url: event.request.url, mode: event.request.mode })
+ )
+});
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers
new file mode 100644
index 0000000000..3c534471c8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/resources/sw.js.headers
@@ -0,0 +1 @@
+Service-Worker-Allowed: /html/semantics/embedded-content/the-img-element/
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html
new file mode 100644
index 0000000000..7189a57642
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ <div style="width: 200px; height: 200px; background-color: green;"></div>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html
new file mode 100644
index 0000000000..60b061f14a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/responsive-image-select-print.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Test print result of responsive image</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#srcset-attribute">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803094">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="responsive-image-select-print-ref.html">
+<body>
+ <picture>
+ <source width="200" srcset="./resources/red.png 1w, ./resources/green.png 200w">
+ <img>
+ </picture>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html
new file mode 100644
index 0000000000..3c26323426
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/scrolling-below-viewport-image-lazy-loading-in-iframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Scrolling a lazy loaded image into view in an iframe</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<iframe onload="async_test(this.contentWindow.run)" srcdoc="
+<!DOCTYPE html>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../resources/common.js'></script>
+
+<h1>Scroll down...</h1>
+<p>Scroll down...</p>
+<p>Scroll down...</p>
+<img id='below_iframe_viewport_img' src='resources/image.png' loading='lazy'
+ onload='below_iframe_viewport_img.resolve();' onerror='below_iframe_viewport_img.reject();'>
+
+<script>
+ const scroll_trigger_img = new ElementLoadPromise('visible');
+ const below_iframe_viewport_img = new ElementLoadPromise('below_iframe_viewport_img');
+
+ function run(t) {
+ below_iframe_viewport_img.element().scrollIntoView();
+ below_iframe_viewport_img.promise
+ .then(t.step_func(() => {
+ assert_not_equals(below_iframe_viewport_img.element().width, 0, 'width should be greater than zero after scrolling');
+ assert_not_equals(below_iframe_viewport_img.element().height, 0, 'height should be greater than zero after scrolling');
+ t.done();
+ }))
+ .catch(t.unreached_func('The below_iframe_viewport image should load'));
+ };
+</script>
+"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html
new file mode 100644
index 0000000000..db61db351e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/implicit-sizes-ignores-width.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<title>Implicit sizes ignores width</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+img {width: auto;}
+</style>
+<img srcset="../srcset/resources/image.png 100w" sizes="400px" id="sizes">
+<img srcset="../srcset/resources/image.png 100w" width="400" id="width">
+<script>
+setup({explicit_done:true});
+onload = () => {
+ test(() => {
+ assert_equals(document.getElementById("sizes").width, 400);
+ assert_equals(document.getElementById("width").width, window.innerWidth);
+ done();
+ });
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html
new file mode 100644
index 0000000000..6aa77ebf85
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-display-none.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (display:none)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="display:none" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=doctype%20html&style=display:none"></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html
new file mode 100644
index 0000000000..2150192d29
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-quirks-mode.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (quirks mode)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="quirks mode" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=----&style="></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html
new file mode 100644
index 0000000000..6e70c88396
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-standards-mode.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (standards mode)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="standards mode" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=doctype%20html&style="></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html
new file mode 100644
index 0000000000..ab3f69e058
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/parse-a-sizes-attribute-width-1000px.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>img parse a sizes attribute (width:1000px)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<iframe data-desc="width:1000px" style="width:1000px; height:1000px" src="support/sizes-iframed.sub.html?doctype=doctype%20html&style=width:1000px%3B%20height:16px"></iframe>
+<script src="support/parse-a-sizes-attribute.js"></script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html
new file mode 100644
index 0000000000..221930fddb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/reference/sizes-auto-rendering-ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<img
+ src="/images/green.png"
+ width="33"
+ height="13"
+>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html
new file mode 100644
index 0000000000..75d1884e34
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-2.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="33"
+ height="13"
+>
+<script>
+ function imageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ testImg.addEventListener('load', imageLoaded);
+ testImg.setAttribute('srcset', `
+ /images/red.png 10w,
+ /images/green.png 100w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html
new file mode 100644
index 0000000000..71ed90ebbe
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-3.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="33"
+ height="13"
+>
+<script>
+ function imageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ testImg.addEventListener('load', imageLoaded);
+ testImg.setAttribute('srcset', `
+ /images/green.png 100w,
+ /images/red.png 1000w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html
new file mode 100644
index 0000000000..901c6f2e09
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering-dynamic.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes dynamic rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="1"
+ height="13"
+>
+<script>
+ function secondImageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ function firstImageLoaded() {
+ const expected = 'red.png';
+ if (!testImg.currentSrc.endsWith('red.png')) {
+ const fail_msg = `FAIL: currentSrc is ${testImg.currentSrc}, expected ${expected}.`;
+ document.body.textContent = fail_msg;
+ takeScreenshot();
+ }
+
+ testImg.addEventListener('load', secondImageLoaded);
+ testImg.style.width = '33px';
+ }
+
+ testImg.addEventListener('load', firstImageLoaded, {once: true});
+ testImg.setAttribute('srcset', `
+ /images/fail.gif 1000w,
+ /images/green.png 100w,
+ /images/red.png 10w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html
new file mode 100644
index 0000000000..e972f69c20
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto-rendering.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Auto sizes rendering</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#sizes-attributes">
+<link rel="match" href="reference/sizes-auto-rendering-ref.html">
+<script src="/common/rendering-utils.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<img
+ id="testImg"
+ loading="lazy"
+ sizes="auto"
+ width="33"
+ height="13"
+>
+<script>
+ function imageLoaded() {
+ waitForAtLeastOneFrame().then(takeScreenshot);
+ }
+
+ testImg.addEventListener('load', imageLoaded);
+ testImg.setAttribute('srcset', `
+ /images/fail.gif 10w,
+ /images/green.png 100w,
+ /images/red.png 1000w
+ `);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html
new file mode 100644
index 0000000000..6c1a741f2b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-auto.html
@@ -0,0 +1,153 @@
+<!doctype html>
+<title>img parse a sizes attribute: sizes=auto</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#parse-a-sizes-attribute">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+img { height: 10px; } /* Keep all images in the viewport, so lazy images load */
+#narrow-div { width: 10px; }
+#wide-div { width: 500px; }
+</style>
+<div id=log></div>
+<script src="support/parse-a-sizes-attribute.js"></script>
+<img srcset='/images/green-1x1.png?ref1 50w, /images/green-16x16.png?ref1 51w' sizes='1px' id=ref1>
+<img srcset='/images/green-1x1.png?ref2 50w, /images/green-16x16.png?ref2 51w' sizes='100vw' id=ref2>
+<div id='narrow-div'></div>
+<div id='wide-div'></div>
+<script>
+"use strict";
+
+// https://github.com/web-platform-tests/rfcs/pull/75
+let async_promise_test = (promise, description) => {
+ async_test(test => {
+ promise(test)
+ .then(() => {test.done();})
+ .catch(test.step_func(error => { throw error; }));
+ }, description);
+};
+
+function check(imgOrPicture) {
+ let img = imgOrPicture;
+ let source;
+ if (imgOrPicture.localName === 'picture') {
+ source = imgOrPicture.firstChild;
+ img = imgOrPicture.lastChild;
+ }
+ const ref = document.getElementById(img.dataset.ref);
+ async_promise_test(async (t) => {
+ let expect = ref.currentSrc;
+ if (expect) {
+ expect = expect.split('?')[0];
+ }
+ if (expect === '' || expect === null || expect === undefined) {
+ assert_unreached('ref currentSrc was ' + format_value(expect));
+ }
+ await new Promise((resolve, reject) => {
+ img.onload = resolve;
+ img.onerror = reject;
+ });
+ t.step(() => {
+ let got = img.currentSrc;
+ assert_greater_than(got.indexOf('?'), -1, 'expected a "?" in currentSrc');
+ got = got.split('?')[0];
+ assert_equals(got, expect);
+ })
+ }, imgOrPicture.outerHTML);
+}
+
+const tests = [
+ // Smoke tests
+ {sizes: '1px', 'data-ref': 'ref1'},
+ {sizes: '', 'data-ref': 'ref2'},
+ // Actual tests
+ {loading: 'lazy', sizes: 'auto', width: '10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'AUTO', width: '10', 'data-ref': 'ref1'},
+ {loading: 'lazy', width: '10', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', style: 'width: 10px', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', style: 'max-width: 10px', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', style: 'width: 100%; max-width: 10px', 'data-ref': 'ref2'}, // no `sizes: 'auto'` -> 100vw
+ {loading: 'lazy', sizes: 'auto', width: '500', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', width: '10', style: 'visibility: hidden', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', width: '10', style: 'display: inline', hidden: '', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', width: '0', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'width: 0px', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', 'data-ref': 'ref2'}, // no width -> UA default of 300px
+ {loading: 'lazy', sizes: 'auto, 100vw', width: '10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: '100vw, auto', width: '10', 'data-ref': 'ref2'},
+ {loading: 'eager', sizes: 'auto', width: '10', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', width: '100%', parent: 'narrow-div', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', width: '100%', parent: 'wide-div', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', style: 'height: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'height: 10px; aspect-ratio: 500 / 10', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-height: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-height: 10px; aspect-ratio: 500 / 10', 'data-ref': 'ref2'},
+ {loading: 'lazy', sizes: 'auto', style: 'inline-size: 10px', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-inline-size: 10px', 'data-ref': 'ref2'}, // no width -> UA default of 300px
+ {loading: 'lazy', sizes: 'auto', style: 'block-size: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-block-size: 10px; aspect-ratio: 10 / 10', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'block-size: 10px; writing-mode: vertical-rl', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-block-size: 10px; writing-mode: vertical-rl', 'data-ref': 'ref2'}, // no width -> UA default of 300px
+ {loading: 'lazy', sizes: 'auto', style: 'inline-size: 10px; aspect-ratio: 10/10; writing-mode: vertical-rl', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'min-inline-size: 10px; aspect-ratio: 10/10; writing-mode: vertical-rl', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: '--my-width: 10px; width: var(--my-width)', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'width: calc(5px + 5px)', 'data-ref': 'ref1'},
+ {loading: 'lazy', sizes: 'auto', style: 'position: absolute; left: 50%; right: 49%', 'data-ref': 'ref2'}, // replaced elements don't get the width resolved from 'left'/'right' per https://drafts.csswg.org/css2/#abs-replaced-width
+];
+
+function test_img(obj, i) {
+ const img = document.createElement('img');
+ let parent = document.body;
+ for (const attr in obj) {
+ if (attr === 'parent') {
+ parent = document.getElementById(obj.parent);
+ } else {
+ img.setAttribute(attr, obj[attr]);
+ }
+ }
+ img.srcset = `/images/green-1x1.png?img${i} 50w, /images/green-16x16.png?img${i} 51w`
+ parent.appendChild(img);
+ check(img);
+}
+
+function test_picture(obj, i) {
+ const picture = document.createElement('picture');
+ const source = document.createElement('source');
+ const img = document.createElement('img');
+ let parent = document.body;
+ for (const attr in obj) {
+ switch (attr) {
+ case 'parent':
+ parent = document.getElementById(obj.parent);
+ break;
+ case 'sizes':
+ // Authors have to specify sizes="auto" on the img to use auto-sizes.
+ if(obj[attr].toLowerCase().startsWith("auto")) {
+ img.setAttribute(attr, obj[attr]);
+ break;
+ }
+ case 'type':
+ case 'media':
+ source.setAttribute(attr, obj[attr]);
+ break;
+ default:
+ img.setAttribute(attr, obj[attr]);
+ break;
+ }
+ }
+ source.srcset = `/images/green-1x1.png?picture${i} 50w, /images/green-16x16.png?picture${i} 51w`;
+ picture.appendChild(source);
+ picture.appendChild(img);
+ parent.appendChild(picture);
+ check(picture);
+}
+
+onload = () => {
+ let i = 0;
+ for (const obj of tests) {
+ i++;
+ test_img(obj, i);
+ test_picture(obj, i);
+ }
+ done();
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html
new file mode 100644
index 0000000000..68466ae94d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>Test reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<iframe width="500" height="500" srcdoc='<!doctype html><img alt="FAIL" srcset="/images/green-256x256.png 100w" style="max-width: 100%" sizes="10px">'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html
new file mode 100644
index 0000000000..51f8145bf9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-001.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Image intrinsic size specified via sizes attribute reacts properly to media changes</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="match" href="sizes-dynamic-001-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149357">
+<script>
+function frameLoaded(frame) {
+ frame.width = "500";
+ let img = frame.contentDocument.querySelector('img');
+
+ // Trigger the viewport resize, which will trigger the image load task.
+ img.offsetWidth;
+
+ // Wait for the image load task to run.
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+}
+</script>
+<iframe onload="frameLoaded(this)" width="200" height="500" srcdoc='<!doctype html><img srcset="/images/green-256x256.png 100w" style="max-width: 100%" sizes="(min-width: 400px) 10px, 20px">'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html
new file mode 100644
index 0000000000..6c64b3da39
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/sizes-dynamic-002.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Image intrinsic size specified via sizes attribute reacts properly to media changes in Shadow DOM</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="match" href="sizes-dynamic-001-ref.html">
+<link rel="help" href="https://html.spec.whatwg.org/#sizes-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149357">
+<script>
+function frameLoaded(frame) {
+ let doc = frame.contentDocument;
+ let shadow = doc.getElementById("host").attachShadow({ mode: "open" });
+
+ let img = doc.createElement("img");
+ img.srcset = "/images/green-256x256.png 100w";
+ img.style.maxWidth = "100%";
+ img.setAttribute("sizes", "(min-width: 400px) 10px, 20px");
+
+ img.onload = function() {
+ img.offsetWidth; // Flush layout.
+
+ frame.width = "500";
+
+ // Trigger the viewport resize, which will trigger the image load task.
+ img.offsetWidth;
+
+ // Wait for the image load task to run.
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+ };
+
+ shadow.appendChild(img);
+}
+</script>
+<iframe onload="frameLoaded(this)" width="200" height="500" srcdoc='<!doctype html><div id="host"></div>'></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js
new file mode 100644
index 0000000000..62ad00a468
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/parse-a-sizes-attribute.js
@@ -0,0 +1,29 @@
+setup({explicit_done:true});
+
+function check(p, iframe) {
+ var current = p.firstElementChild;
+ var ref_sizes = current.getAttribute('sizes');
+ var expect = current.currentSrc;
+ if (expect) {
+ expect = expect.split('?')[0];
+ }
+ while (current = current.nextElementSibling) {
+ test(function() {
+ if (expect === '' || expect === null || expect === undefined) {
+ assert_unreached('ref currentSrc was ' + format_value(expect));
+ }
+ var got = current.currentSrc;
+ assert_greater_than(got.indexOf('?'), -1, 'expected a "?" in currentSrc');
+ got = got.split('?')[0];
+ assert_equals(got, expect);
+ }, current.outerHTML + ' ref sizes=' + format_value(ref_sizes) + ' (' + iframe.getAttribute('data-desc') + ')');
+ }
+}
+
+onload = function() {
+ var iframe = document.querySelector('iframe');
+ [].forEach.call(iframe.contentDocument.querySelectorAll('p'), function(p) {
+ check(p, iframe);
+ });
+ done();
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html
new file mode 100644
index 0000000000..1f80ad2f91
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/sizes/support/sizes-iframed.sub.html
@@ -0,0 +1,186 @@
+<!{{GET[doctype]}}>
+<style> img { {{GET[style]}} } </style>
+<!-- First <img> in a <p> is the reference. The following <img>s should be equivalent -->
+<!-- default is 100vw, not 300px -->
+<p>
+<img srcset='/images/green-1x1.png?a1 300w, /images/green-16x16.png?a1 301w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?a2 300w, /images/green-16x16.png?a2 301w'>
+<p>
+<img srcset='/images/green-1x1.png?b1 450w, /images/green-16x16.png?b1 451w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?b2 450w, /images/green-16x16.png?b2 451w'>
+<p>
+<img srcset='/images/green-1x1.png?c1 600w, /images/green-16x16.png?c1 601w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?c2 600w, /images/green-16x16.png?c2 601w'>
+<p>
+<img srcset='/images/green-1x1.png?d1 900w, /images/green-16x16.png?d1 901w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?d2 900w, /images/green-16x16.png?d2 901w'>
+
+<p>
+<img srcset='/images/green-1x1.png?e1 50w, /images/green-16x16.png?e1 51w' sizes='1px'>
+<img srcset='/images/green-1x1.png?e2 50w, /images/green-16x16.png?e2 51w' sizes='0'>
+<img srcset='/images/green-1x1.png?e3 50w, /images/green-16x16.png?e3 51w' sizes='-0'>
+<img srcset='/images/green-1x1.png?e4 50w, /images/green-16x16.png?e4 51w' sizes='+0'>
+<img srcset='/images/green-1x1.png?e5 50w, /images/green-16x16.png?e5 51w' sizes='+1px'>
+<img srcset='/images/green-1x1.png?e6 50w, /images/green-16x16.png?e6 51w' sizes='.1px'>
+<img srcset='/images/green-1x1.png?e7 50w, /images/green-16x16.png?e7 51w' sizes='0.1em'>
+<img srcset='/images/green-1x1.png?e8 50w, /images/green-16x16.png?e8 51w' sizes='0.1ex'>
+<img srcset='/images/green-1x1.png?e9 50w, /images/green-16x16.png?e9 51w' sizes='0.1ch'>
+<img srcset='/images/green-1x1.png?e10 50w, /images/green-16x16.png?e10 51w' sizes='0.1rem'>
+<img srcset='/images/green-1x1.png?e11 50w, /images/green-16x16.png?e11 51w' sizes='0.1vw'>
+<img srcset='/images/green-1x1.png?e12 50w, /images/green-16x16.png?e12 51w' sizes='0.1vh'>
+<img srcset='/images/green-1x1.png?e13 50w, /images/green-16x16.png?e13 51w' sizes='0.1vmin'>
+<img srcset='/images/green-1x1.png?e14 50w, /images/green-16x16.png?e14 51w' sizes='0.1vmax'>
+<img srcset='/images/green-1x1.png?e15 50w, /images/green-16x16.png?e15 51w' sizes='0.1cm'>
+<img srcset='/images/green-1x1.png?e16 50w, /images/green-16x16.png?e16 51w' sizes='1mm'>
+<img srcset='/images/green-1x1.png?e17 50w, /images/green-16x16.png?e17 51w' sizes='1q'>
+<img srcset='/images/green-1x1.png?e18 50w, /images/green-16x16.png?e18 51w' sizes='0.01in'>
+<img srcset='/images/green-1x1.png?e19 50w, /images/green-16x16.png?e19 51w' sizes='0.1pc'>
+<img srcset='/images/green-1x1.png?e20 50w, /images/green-16x16.png?e20 51w' sizes='0.1pt'>
+<img srcset='/images/green-1x1.png?e21 50w, /images/green-16x16.png?e21 51w' sizes='/* */1px/* */'>
+<img srcset='/images/green-1x1.png?e22 50w, /images/green-16x16.png?e22 51w' sizes=' /**/ /**/ 1px /**/ /**/ '>
+<img srcset='/images/green-1x1.png?e23 50w, /images/green-16x16.png?e23 51w' sizes='(),1px'>
+<img srcset='/images/green-1x1.png?e24 50w, /images/green-16x16.png?e24 51w' sizes='x(),1px'>
+<img srcset='/images/green-1x1.png?e25 50w, /images/green-16x16.png?e25 51w' sizes='{},1px'>
+<img srcset='/images/green-1x1.png?e26 50w, /images/green-16x16.png?e26 51w' sizes='[],1px'>
+<img srcset='/images/green-1x1.png?e27 50w, /images/green-16x16.png?e27 51w' sizes='1px,('>
+<img srcset='/images/green-1x1.png?e28 50w, /images/green-16x16.png?e28 51w' sizes='1px,x('>
+<img srcset='/images/green-1x1.png?e29 50w, /images/green-16x16.png?e29 51w' sizes='1px,{'>
+<img srcset='/images/green-1x1.png?e30 50w, /images/green-16x16.png?e30 51w' sizes='1px,['>
+<img srcset='/images/green-1x1.png?e31 50w, /images/green-16x16.png?e31 51w' sizes='\(,1px'>
+<img srcset='/images/green-1x1.png?e32 50w, /images/green-16x16.png?e32 51w' sizes='x\(,1px'>
+<img srcset='/images/green-1x1.png?e33 50w, /images/green-16x16.png?e33 51w' sizes='\{,1px'>
+<img srcset='/images/green-1x1.png?e34 50w, /images/green-16x16.png?e34 51w' sizes='\[,1px'>
+<img srcset='/images/green-1x1.png?e35 50w, /images/green-16x16.png?e35 51w' sizes='1\p\x'>
+<img srcset='/images/green-1x1.png?e36 50w, /images/green-16x16.png?e36 51w' sizes='calc(1px)'>
+<img srcset='/images/green-1x1.png?e36a 50w, /images/green-16x16.png?e36a 51w' sizes='min(1px, 100px)'>
+<img srcset='/images/green-1x1.png?e36b 50w, /images/green-16x16.png?e36b 51w' sizes='min(-100px, 1px)'>
+<img srcset='/images/green-1x1.png?e37 50w, /images/green-16x16.png?e37 51w' sizes='(min-width:0) calc(1px)'>
+<img srcset='/images/green-1x1.png?e37a 50w, /images/green-16x16.png?e37a 51w' sizes='(min-width:0) min(1px, 100px)'>
+<img srcset='/images/green-1x1.png?e37b 50w, /images/green-16x16.png?e37b 51w' sizes='(min-width:0) max(-100px, 1px)'>
+<img srcset='/images/green-1x1.png?e38 50w, /images/green-16x16.png?e38 51w' sizes='(min-width:calc(0)) 1px'>
+<img srcset='/images/green-1x1.png?e39 50w, /images/green-16x16.png?e39 51w' sizes='(min-width:0) 1px, 100vw'>
+<img srcset='/images/green-1x1.png?e40 50w, /images/green-16x16.png?e40 51w' sizes='(min-width:0) 1px, (min-width:0) 100vw, 100vw'>
+<img srcset='/images/green-1x1.png?e41 50w, /images/green-16x16.png?e41 51w' sizes='(min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?e42 50w, /images/green-16x16.png?e42 51w' sizes='not (min-width:0) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e43 50w, /images/green-16x16.png?e43 51w' sizes='(min-width:unknown-mf-value) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e44 50w, /images/green-16x16.png?e44 51w' sizes='not (min-width:unknown-mf-value) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e45 50w, /images/green-16x16.png?e45 51w' sizes='(min-width:-1px) 1px, 100vw'>
+<img srcset='/images/green-1x1.png?e46 50w, /images/green-16x16.png?e46 51w' sizes='not (min-width:-1px) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e47 50w, /images/green-16x16.png?e47 51w' sizes='(unknown-mf-name) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e48 50w, /images/green-16x16.png?e48 51w' sizes='not (unknown-mf-name) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e49 50w, /images/green-16x16.png?e49 51w' sizes='(unknown "general-enclosed") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e50 50w, /images/green-16x16.png?e50 51w' sizes='not (unknown "general-enclosed") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e51 50w, /images/green-16x16.png?e51 51w' sizes='unknown-general-enclosed(foo) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e52 50w, /images/green-16x16.png?e52 51w' sizes='not unknown-general-enclosed(foo) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e53 50w, /images/green-16x16.png?e53 51w' sizes='print 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e54 50w, /images/green-16x16.png?e54 51w' sizes='not print 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e55 50w, /images/green-16x16.png?e55 51w' sizes='unknown-media-type 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e56 50w, /images/green-16x16.png?e56 51w' sizes='not unknown-media-type 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e57 50w, /images/green-16x16.png?e57 51w' sizes='(min-width:0) or (min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?e58 50w, /images/green-16x16.png?e58 51w' sizes='(min-width:0) or (unknown-mf-name) 1px'>
+<img srcset='/images/green-1x1.png?e59 50w, /images/green-16x16.png?e59 51w' sizes='(min-width:0) or (min-width:unknown-mf-value) 1px'>
+<img srcset='/images/green-1x1.png?e60 50w, /images/green-16x16.png?e60 51w' sizes='(min-width:0) or (min-width:-1px) 1px'>
+<img srcset='/images/green-1x1.png?e61 50w, /images/green-16x16.png?e61 51w' sizes='(min-width:0) or (unknown "general-enclosed") 1px'>
+<img srcset='/images/green-1x1.png?e62 50w, /images/green-16x16.png?e62 51w' sizes='(min-width:0) or unknown-general-enclosed(foo) 1px'>
+<img srcset='/images/green-1x1.png?e63 50w, /images/green-16x16.png?e63 51w' sizes='(min-width:0) or (]) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e64 50w, /images/green-16x16.png?e64 51w' sizes='(min-width:0) or unknown-media-type 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e65 50w, /images/green-16x16.png?e65 51w' sizes='(123) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e66 50w, /images/green-16x16.png?e66 51w' sizes='not (123) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e67 50w, /images/green-16x16.png?e67 51w' sizes='(!) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e68 50w, /images/green-16x16.png?e68 51w' sizes='not (!) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e69 50w, /images/green-16x16.png?e69 51w' sizes='! 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e70 50w, /images/green-16x16.png?e70 51w' sizes='not ! 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e71 50w, /images/green-16x16.png?e71 51w' sizes='(]) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e72 50w, /images/green-16x16.png?e72 51w' sizes='not (]) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e73 50w, /images/green-16x16.png?e73 51w' sizes='] 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e74 50w, /images/green-16x16.png?e74 51w' sizes='not ] 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e75 50w, /images/green-16x16.png?e75 51w' sizes='(}) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e76 50w, /images/green-16x16.png?e76 51w' sizes='not (}) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e77 50w, /images/green-16x16.png?e77 51w' sizes='} 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e78 50w, /images/green-16x16.png?e78 51w' sizes='not } 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e79 50w, /images/green-16x16.png?e79 51w' sizes=') 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e80 50w, /images/green-16x16.png?e80 51w' sizes='not ) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e81 50w, /images/green-16x16.png?e81 51w' sizes='(;) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e82 50w, /images/green-16x16.png?e82 51w' sizes='not (;) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e83 50w, /images/green-16x16.png?e83 51w' sizes='(.) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e84 50w, /images/green-16x16.png?e84 51w' sizes='not (.) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e85 50w, /images/green-16x16.png?e85 51w' sizes='; 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e86 50w, /images/green-16x16.png?e86 51w' sizes='not ; 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e87 50w, /images/green-16x16.png?e87 51w' sizes=', 1px'>
+<img srcset='/images/green-1x1.png?e88 50w, /images/green-16x16.png?e88 51w' sizes='1px,'>
+<img srcset='/images/green-1x1.png?e89 50w, /images/green-16x16.png?e89 51w' sizes='(min-width:0) 1px,'>
+<img srcset='/images/green-1x1.png?e90 50w, /images/green-16x16.png?e90 51w' sizes='-0e-0px'>
+<img srcset='/images/green-1x1.png?e91 50w, /images/green-16x16.png?e91 51w' sizes='+0.11e+01px'>
+<img srcset='/images/green-1x1.png?e92 50w, /images/green-16x16.png?e92 51w' sizes='0.2e1px'>
+<img srcset='/images/green-1x1.png?e93 50w, /images/green-16x16.png?e93 51w' sizes='0.3E1px'>
+<img srcset='/images/green-1x1.png?e94 50w, /images/green-16x16.png?e94 51w' sizes='.4E1px'>
+<img srcset='/images/green-1x1.png?e95 50w, /images/green-16x16.png?e95 51w' sizes='all 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e96 50w, /images/green-16x16.png?e96 51w' sizes='all and (min-width:0) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e97 50w, /images/green-16x16.png?e97 51w' sizes='min-width:0 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e98 50w, /images/green-16x16.png?e98 51w' sizes='1px, 100vw'>
+<img srcset='/images/green-1x1.png?e99 50w, /images/green-16x16.png?e99 51w' sizes='1px, (min-width:0) 100vw'>
+<img srcset='/images/green-1x1.png?e100 50w, /images/green-16x16.png?e100 51w' sizes='1px, foo bar'>
+<img srcset='/images/green-1x1.png?e101 50w, /images/green-16x16.png?e101 51w' sizes='(min-width:0) 1px, foo bar'>
+<img srcset='/images/green-1x1.png?e102 50w, /images/green-16x16.png?e102 51w' sizes='("grammar does not match") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e103 50w, /images/green-16x16.png?e103 51w' sizes='not ("grammar does not match") 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e104 50w, /images/green-16x16.png?e104 51w' sizes='(unknown-general-enclosed !) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e105 50w, /images/green-16x16.png?e105 51w' sizes='not (unknown-general-enclosed !) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e106 50w, /images/green-16x16.png?e106 51w' sizes='(min-width:0) or (unknown-general-enclosed !) 1px'>
+<img srcset='/images/green-1x1.png?e107 50w, /images/green-16x16.png?e107 51w' sizes='not ((min-width:0) or (unknown "general-enclosed")) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e108 50w, /images/green-16x16.png?e108 51w' sizes='(max-width:0) or (unknown-general-enclosed !) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?e109 50w, /images/green-16x16.png?e109 51w' sizes='not ((max-width:0) or (unknown "general-enclosed")) 100vw, 1px'>
+<img srcset='/images/green-1x1.png?f48 50w, /images/green-16x16.png?f48 51w' sizes='calc(1px'>
+<img srcset='/images/green-1x1.png?f48a 50w, /images/green-16x16.png?f48a 51w' sizes='min(1px, 200vw'>
+<img srcset='/images/green-1x1.png?f48b 50w, /images/green-16x16.png?f48b 51w' sizes='max(-200vw, 1px'>
+<img srcset='/images/green-1x1.png?f49 50w, /images/green-16x16.png?f49 51w' sizes='(min-width:0) calc(1px'>
+<img srcset='/images/green-1x1.png?f49a 50w, /images/green-16x16.png?f49a 51w' sizes='(min-width:0) min(1px, 200vw'>
+<img srcset='/images/green-1x1.png?f49b 50w, /images/green-16x16.png?f49b 51w' sizes='(min-width:0) max(-200vw, 1px'>
+
+<p>
+<img srcset='/images/green-1x1.png?f1 50w, /images/green-16x16.png?f1 51w' sizes='100vw'>
+<img srcset='/images/green-1x1.png?f2 50w, /images/green-16x16.png?f2 51w' sizes=''>
+<img srcset='/images/green-1x1.png?f3 50w, /images/green-16x16.png?f3 51w' sizes=','>
+<img srcset='/images/green-1x1.png?f4 50w, /images/green-16x16.png?f4 51w' sizes='-1px'>
+<img srcset='/images/green-1x1.png?f5 50w, /images/green-16x16.png?f5 51w' sizes='1'>
+<img srcset='/images/green-1x1.png?f6 50w, /images/green-16x16.png?f6 51w' sizes='0.1%'>
+<img srcset='/images/green-1x1.png?f7 50w, /images/green-16x16.png?f7 51w' sizes='0.1deg'>
+<img srcset='/images/green-1x1.png?f8 50w, /images/green-16x16.png?f8 51w' sizes='0.1grad'>
+<img srcset='/images/green-1x1.png?f9 50w, /images/green-16x16.png?f9 51w' sizes='0.1rad'>
+<img srcset='/images/green-1x1.png?f10 50w, /images/green-16x16.png?f10 51w' sizes='0.1turn'>
+<img srcset='/images/green-1x1.png?f11 50w, /images/green-16x16.png?f11 51w' sizes='0.1s'>
+<img srcset='/images/green-1x1.png?f12 50w, /images/green-16x16.png?f12 51w' sizes='0.1ms'>
+<img srcset='/images/green-1x1.png?f13 50w, /images/green-16x16.png?f13 51w' sizes='0.1Hz'>
+<img srcset='/images/green-1x1.png?f14 50w, /images/green-16x16.png?f14 51w' sizes='0.1kHz'>
+<img srcset='/images/green-1x1.png?f15 50w, /images/green-16x16.png?f15 51w' sizes='0.1dpi'>
+<img srcset='/images/green-1x1.png?f16 50w, /images/green-16x16.png?f16 51w' sizes='0.1dpcm'>
+<img srcset='/images/green-1x1.png?f17 50w, /images/green-16x16.png?f17 51w' sizes='0.1dppx'>
+<img srcset='/images/green-1x1.png?f17a 50w, /images/green-16x16.png?f17a 51w' sizes='0.1x'>
+<img srcset='/images/green-1x1.png?f18 50w, /images/green-16x16.png?f18 51w' data-foo='1px' sizes='attr(data-foo, length, 1px)'>
+<img srcset='/images/green-1x1.png?f19 50w, /images/green-16x16.png?f19 51w' data-foo='1' sizes='attr(data-foo, px, 1px)'>
+<img srcset='/images/green-1x1.png?f20 50w, /images/green-16x16.png?f20 51w' sizes='toggle(1px)'>
+<img srcset='/images/green-1x1.png?f21 50w, /images/green-16x16.png?f21 51w' sizes='inherit'>
+<img srcset='/images/green-1x1.png?f23 50w, /images/green-16x16.png?f23 51w' sizes='initial'>
+<img srcset='/images/green-1x1.png?f24 50w, /images/green-16x16.png?f24 51w' sizes='unset'>
+<img srcset='/images/green-1x1.png?f25 50w, /images/green-16x16.png?f25 51w' sizes='default'>
+<img srcset='/images/green-1x1.png?f26 50w, /images/green-16x16.png?f26 51w' sizes='1/* */px'>
+<img srcset='/images/green-1x1.png?f27 50w, /images/green-16x16.png?f27 51w' sizes='1p/* */x'>
+<img srcset='/images/green-1x1.png?f28 50w, /images/green-16x16.png?f28 51w' sizes='-/**/0'>
+<img srcset='/images/green-1x1.png?f29 50w, /images/green-16x16.png?f29 51w' sizes='((),1px'>
+<img srcset='/images/green-1x1.png?f30 50w, /images/green-16x16.png?f30 51w' sizes='x(x(),1px'>
+<img srcset='/images/green-1x1.png?f31 50w, /images/green-16x16.png?f31 51w' sizes='{{},1px'>
+<img srcset='/images/green-1x1.png?f32 50w, /images/green-16x16.png?f32 51w' sizes='[[],1px'>
+<img srcset='/images/green-1x1.png?f33 50w, /images/green-16x16.png?f33 51w' sizes='1px !important'>
+<img srcset='/images/green-1x1.png?f34 50w, /images/green-16x16.png?f34 51w' sizes='\1px'>
+<img srcset='/images/green-1x1.png?f35 50w, /images/green-16x16.png?f35 51w' sizes='all 1px'>
+<img srcset='/images/green-1x1.png?f36 50w, /images/green-16x16.png?f36 51w' sizes='all and (min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?f37 50w, /images/green-16x16.png?f37 51w' sizes='min-width:0 1px'>
+<img srcset='/images/green-1x1.png?f38 50w, /images/green-16x16.png?f38 51w' sizes='100vw, 1px'>
+<img srcset='/images/green-1x1.png?f39 50w, /images/green-16x16.png?f39 51w' sizes='100vw, (min-width:0) 1px'>
+<img srcset='/images/green-1x1.png?f40 50w, /images/green-16x16.png?f40 51w' sizes='foo bar'>
+<img srcset='/images/green-1x1.png?f41 50w, /images/green-16x16.png?f41 51w' sizes='foo-bar'>
+<img srcset='/images/green-1x1.png?f42 50w, /images/green-16x16.png?f42 51w' sizes='(min-width:0) 1px foo bar'>
+<img srcset='/images/green-1x1.png?f43 50w, /images/green-16x16.png?f43 51w' sizes='(min-width:0) 0.1%'>
+<img srcset='/images/green-1x1.png?f44 50w, /images/green-16x16.png?f44 51w' sizes='(min-width:0) 1'>
+<img srcset='/images/green-1x1.png?f45 50w, /images/green-16x16.png?f45 51w' sizes='-1e0px'>
+<img srcset='/images/green-1x1.png?f46 50w, /images/green-16x16.png?f46 51w' sizes='1e1.5px'>
+<img srcset='/images/green-1x1.png?f47 50w, /images/green-16x16.png?f47 51w' style='--foo: 1px' sizes='var(--foo)'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html
new file mode 100644
index 0000000000..5997e14e4b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Image source selection using media queries is performed for img elements outside the document</title>
+<link rel="help" href="https://html.spec.whatwg.org/#reacting-to-environment-changes">
+<link rel="help" href="https://html.spec.whatwg.org/#reacting-to-dom-mutations">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe width="350" height="100" onload="async_test(this.contentWindow.run)" srcdoc="
+<!DOCTYPE html>
+<script>
+const { assert_equals } = parent;
+const iframe = parent.document.querySelector('iframe');
+
+function run(t) {
+ const picture = document.createElement('picture');
+
+ const source1 = document.createElement('source');
+ source1.setAttribute('media', '(min-width: 300px)');
+ source1.setAttribute('srcset', 'data:,a');
+ picture.append(source1);
+
+ const source2 = document.createElement('source');
+ source2.setAttribute('media', '(min-width: 200px)');
+ source2.setAttribute('srcset', 'data:,b');
+ picture.append(source2);
+
+ const img = document.createElement('img');
+ img.src = 'data:,c';
+ picture.append(img);
+
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, 'data:,a', 'Initial currentSrc value');
+ matchMedia(source1.media).addEventListener(
+ 'change',
+ function() {
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, 'data:,b', 'After MQ change');
+ img.remove();
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, 'data:,c', 'After removing img');
+ t.done();
+ }));
+ }));
+ },
+ { once: true }
+ );
+ iframe.width = 250;
+ }));
+}
+</script>
+"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html
new file mode 100644
index 0000000000..52366dcaa7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/avoid-reload-on-resize.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Avoid srcset image reloads when viewport resizes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({single_test:true});
+const image_was_loaded = () => {
+ const iframe = document.getElementById("iframe");
+ // Resize the iframe
+ iframe.width="400";
+ // Wait 500 ms
+ step_timeout(() => {
+ // Check that the iframe only loaded a single resource
+ const entries = iframe.contentWindow.performance.getEntriesByType("resource");
+ assert_equals(entries.length, 1);
+ done();
+ }, 500);
+}
+</script>
+<iframe id=iframe width="401" src="resources/resized.html" onload="image_was_loaded()"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js
new file mode 100644
index 0000000000..d4d2c7534c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/common.js
@@ -0,0 +1,25 @@
+setup({explicit_done:true});
+
+function check(img) {
+ var name = format_value(img.getAttribute('srcset'));
+ if (img.hasAttribute('sizes')) {
+ name += ' sizes=' + format_value(img.getAttribute('sizes'));
+ }
+ if (img.hasAttribute('data-desc')) {
+ name += ' (' + img.getAttribute('data-desc') + ')';
+ }
+ test(function() {
+ var expect = img.dataset.expect;
+ if ('resolve' in img.dataset) {
+ var a = document.createElement('a');
+ a.href = expect;
+ expect = a.href;
+ }
+ assert_equals(img.currentSrc, expect);
+ }, name);
+}
+
+onload = function() {
+ [].forEach.call(document.images, check);
+ done();
+};
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html
new file mode 100644
index 0000000000..ce1e4cebe5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html
@@ -0,0 +1,245 @@
+<!doctype html>
+<title>img parse a srcset attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=common.js></script>
+<div id=log></div>
+<!-- splitting loop -->
+<img srcset='' data-expect=''>
+<img srcset=',' data-expect=''>
+<img srcset=',,,' data-expect=''>
+<img srcset=' data:,a 1x ' data-expect='data:,a'>
+<img srcset='&#x9;&#x9;data:,a&#x9;&#x9;1x&#x9;&#x9;' data-expect='data:,a'>
+<img srcset='&#xA;&#xA;data:,a&#xA;&#xA;1x&#xA;&#xA;' data-expect='data:,a'>
+<img srcset='&#xB;&#xB;data:,a&#xB;&#xB;1x&#xB;&#xB;' data-expect='&#xB;&#xB;data:,a&#xB;&#xB;1x&#xB;&#xB;' data-resolve>
+<img srcset='&#xC;&#xC;data:,a&#xC;&#xC;1x&#xC;&#xC;' data-expect='data:,a'>
+<img srcset='&#xD;&#xD;data:,a&#xD;&#xD;1x&#xD;&#xD;' data-expect='data:,a'>
+<img srcset='&#xE;&#xE;data:,a&#xE;&#xE;1x&#xE;&#xE;' data-expect='&#xE;&#xE;data:,a&#xE;&#xE;1x&#xE;&#xE;' data-resolve>
+<img srcset='&#xF;&#xF;data:,a&#xF;&#xF;1x&#xF;&#xF;' data-expect='&#xF;&#xF;data:,a&#xF;&#xF;1x&#xF;&#xF;' data-resolve>
+<img srcset='&#x10;&#x10;data:,a&#x10;&#x10;1x&#x10;&#x10;' data-expect='&#x10;&#x10;data:,a&#x10;&#x10;1x&#x10;&#x10;' data-resolve>
+<img srcset='data:,a' data-expect='data:,a'>
+<img srcset='data:,a ' data-expect='data:,a'>
+<img srcset='data:,a ,' data-expect='data:,a'>
+<img srcset='data:,a,' data-expect='data:,a'>
+<img srcset='data:,a, ' data-expect='data:,a'>
+<img srcset='data:,a,,,' data-expect='data:,a'>
+<img srcset='data:,a,, , ' data-expect='data:,a'>
+<img srcset=' data:,a' data-expect='data:,a'>
+<img srcset=',,,data:,a' data-expect='data:,a'>
+<img srcset=' , ,,data:,a' data-expect='data:,a'>
+<img srcset='&nbsp;data:,a' data-expect='&nbsp;data:,a' data-resolve>
+<img srcset='data:,a&nbsp;' data-expect='data:,a&nbsp;' data-resolve>
+<!-- descriptor tokenizer -->
+<img srcset='data:,a 1x' data-expect='data:,a'>
+<img srcset='data:,a 1x ' data-expect='data:,a'>
+<img srcset='data:,a 1x,' data-expect='data:,a'>
+<img srcset='data:,a ( , data:,b 1x, ), data:,c' data-expect='data:,c'>
+<img srcset='data:,a ((( , data:,b 1x, ), data:,c' data-expect='data:,c'>
+<img srcset='data:,a [ , data:,b 1x, ], data:,c' data-expect='data:,b'>
+<img srcset='data:,a { , data:,b 1x, }, data:,c' data-expect='data:,b'>
+<img srcset='data:,a " , data:,b 1x, ", data:,c' data-expect='data:,b'>
+<img srcset='data:,a \,data:;\,b, data:,c' data-expect='data:;\,b'>
+<img srcset='data:,a, data:,b (' data-expect='data:,a'>
+<img srcset='data:,a, data:,b ( ' data-expect='data:,a'>
+<img srcset='data:,a, data:,b (,' data-expect='data:,a'>
+<img srcset='data:,a, data:,b (x' data-expect='data:,a'>
+<img srcset='data:,a, data:,b ()' data-expect='data:,a'>
+<img srcset='data:,a (, data:,b' data-expect=''>
+<img srcset='data:,a /*, data:,b, data:,c */' data-expect='data:,b'>
+<img srcset='data:,a //, data:,b' data-expect='data:,b'>
+<!-- descriptor parser -->
+<img srcset='data:,a foo' data-expect=''>
+<img srcset='data:,a foo foo' data-expect=''>
+<img srcset='data:,a foo 1x' data-expect=''>
+<img srcset='data:,a foo 1x foo' data-expect=''>
+<img srcset='data:,a foo 1w' data-expect=''>
+<img srcset='data:,a foo 1w foo' data-expect=''>
+<img srcset='data:,a 1x 1x' data-expect=''>
+<img srcset='data:,a 1w 1w' data-expect=''>
+<img srcset='data:,a 1w 1x' data-expect=''>
+<img srcset='data:,a 1x 1w' data-expect=''>
+<img srcset='data:,a 1w 1h' data-expect='data:,a'><!-- should fail for x-only impl -->
+<img srcset='data:,a 1h 1w' data-expect='data:,a'><!-- should fail for x-only impl -->
+<img srcset='data:,a 1h 1h' data-expect=''>
+<img srcset='data:,a 1h 1x' data-expect=''>
+<img srcset='data:,a 1h 1w 1x' data-expect=''>
+<img srcset='data:,a 1x 1w 1h' data-expect=''>
+<img srcset='data:,a 1w' data-expect='data:,a'><!-- should fail for x-only impl -->
+<img srcset='data:,a 1h' data-expect=''>
+<img srcset='data:,a 1h foo' data-expect=''>
+<img srcset='data:,a foo 1h' data-expect=''>
+<img srcset='data:,a 0w' data-expect=''>
+<img srcset='data:,a -1w' data-expect=''>
+<img srcset='data:,a 1w -1w' data-expect=''>
+<img srcset='data:,a 1.0w' data-expect=''>
+<img srcset='data:,a 1w 1.0w' data-expect=''>
+<img srcset='data:,a 1e0w' data-expect=''>
+<img srcset='data:,a 1w 1e0w' data-expect=''>
+<img srcset='data:,a 1www' data-expect=''>
+<img srcset='data:,a 1w 1www' data-expect=''>
+<img srcset='data:,a +1w' data-expect=''>
+<img srcset='data:,a 1w +1w' data-expect=''>
+<img srcset='data:,a 1W' data-expect=''>
+<img srcset='data:,a 1w 1W' data-expect=''>
+<img srcset='data:,a Infinityw' data-expect=''>
+<img srcset='data:,a 1w Infinityw' data-expect=''>
+<img srcset='data:,a NaNw' data-expect=''>
+<img srcset='data:,a 1w NaNw' data-expect=''>
+<img srcset='data:,a 0x1w' data-expect=''>
+<img srcset='data:,a 0X1w' data-expect=''>
+<img srcset='data:,a 1&#x1;w' data-expect='' data-desc='trailing U+0001'>
+<img srcset='data:,a 1&nbsp;w' data-expect='' data-desc='trailing U+00A0'>
+<img srcset='data:,a 1&#x1680;w' data-expect='' data-desc='trailing U+1680'>
+<img srcset='data:,a 1&#x2000;w' data-expect='' data-desc='trailing U+2000'>
+<img srcset='data:,a 1&#x2001;w' data-expect='' data-desc='trailing U+2001'>
+<img srcset='data:,a 1&#x2002;w' data-expect='' data-desc='trailing U+2002'>
+<img srcset='data:,a 1&#x2003;w' data-expect='' data-desc='trailing U+2003'>
+<img srcset='data:,a 1&#x2004;w' data-expect='' data-desc='trailing U+2004'>
+<img srcset='data:,a 1&#x2005;w' data-expect='' data-desc='trailing U+2005'>
+<img srcset='data:,a 1&#x2006;w' data-expect='' data-desc='trailing U+2006'>
+<img srcset='data:,a 1&#x2007;w' data-expect='' data-desc='trailing U+2007'>
+<img srcset='data:,a 1&#x2008;w' data-expect='' data-desc='trailing U+2008'>
+<img srcset='data:,a 1&#x2009;w' data-expect='' data-desc='trailing U+2009'>
+<img srcset='data:,a 1&#x200A;w' data-expect='' data-desc='trailing U+200A'>
+<img srcset='data:,a 1&#x200C;w' data-expect='' data-desc='trailing U+200C'>
+<img srcset='data:,a 1&#x200D;w' data-expect='' data-desc='trailing U+200D'>
+<img srcset='data:,a 1&#x202F;w' data-expect='' data-desc='trailing U+202F'>
+<img srcset='data:,a 1&#x205F;w' data-expect='' data-desc='trailing U+205F'>
+<img srcset='data:,a 1&#x3000;w' data-expect='' data-desc='trailing U+3000'>
+<img srcset='data:,a 1&#xFEFF;w' data-expect='' data-desc='trailing U+FEFF'>
+<img srcset='data:,a &#x1;1w' data-expect='' data-desc='leading U+0001'>
+<img srcset='data:,a &nbsp;1w' data-expect='' data-desc='leading U+00A0'>
+<img srcset='data:,a &#x1680;1w' data-expect='' data-desc='leading U+1680'>
+<img srcset='data:,a &#x2000;1w' data-expect='' data-desc='leading U+2000'>
+<img srcset='data:,a &#x2001;1w' data-expect='' data-desc='leading U+2001'>
+<img srcset='data:,a &#x2002;1w' data-expect='' data-desc='leading U+2002'>
+<img srcset='data:,a &#x2003;1w' data-expect='' data-desc='leading U+2003'>
+<img srcset='data:,a &#x2004;1w' data-expect='' data-desc='leading U+2004'>
+<img srcset='data:,a &#x2005;1w' data-expect='' data-desc='leading U+2005'>
+<img srcset='data:,a &#x2006;1w' data-expect='' data-desc='leading U+2006'>
+<img srcset='data:,a &#x2007;1w' data-expect='' data-desc='leading U+2007'>
+<img srcset='data:,a &#x2008;1w' data-expect='' data-desc='leading U+2008'>
+<img srcset='data:,a &#x2009;1w' data-expect='' data-desc='leading U+2009'>
+<img srcset='data:,a &#x200A;1w' data-expect='' data-desc='leading U+200A'>
+<img srcset='data:,a &#x200C;1w' data-expect='' data-desc='leading U+200C'>
+<img srcset='data:,a &#x200D;1w' data-expect='' data-desc='leading U+200D'>
+<img srcset='data:,a &#x202F;1w' data-expect='' data-desc='leading U+202F'>
+<img srcset='data:,a &#x205F;1w' data-expect='' data-desc='leading U+205F'>
+<img srcset='data:,a &#x3000;1w' data-expect='' data-desc='leading U+3000'>
+<img srcset='data:,a &#xFEFF;1w' data-expect='' data-desc='leading U+FEFF'>
+<img srcset='data:,a 0x' data-expect='data:,a'>
+<img srcset='data:,a -0x' data-expect='data:,a'>
+<img srcset='data:,a 1x -0x' data-expect=''>
+<img srcset='data:,a -1x' data-expect=''>
+<img srcset='data:,a 1x -1x' data-expect=''>
+<img srcset='data:,a 1e0x' data-expect='data:,a'>
+<img srcset='data:,a 1E0x' data-expect='data:,a'>
+<img srcset='data:,a 1e-1x' data-expect='data:,a'>
+<img srcset='data:,a 1.5e1x' data-expect='data:,a'>
+<img srcset='data:,a -x' data-expect=''>
+<img srcset='data:,a .x' data-expect=''>
+<img srcset='data:,a -.x' data-expect=''>
+<img srcset='data:,a 1.x' data-expect=''>
+<img srcset='data:,a .5x' data-expect='data:,a'>
+<img srcset='data:,a .5e1x' data-expect='data:,a'>
+<img srcset='data:,a 1x 1.5e1x' data-expect=''>
+<img srcset='data:,a 1x 1e1.5x' data-expect=''>
+<img srcset='data:,a 1.0x' data-expect='data:,a'>
+<img srcset='data:,a 1x 1.0x' data-expect=''>
+<img srcset='data:,a +1x' data-expect=''>
+<img srcset='data:,a 1X' data-expect=''>
+<img srcset='data:,a Infinityx' data-expect=''>
+<img srcset='data:,a NaNx' data-expect=''>
+<img srcset='data:,a 0x1x' data-expect=''>
+<img srcset='data:,a 0X1x' data-expect=''>
+<img srcset='data:,a 1&#x1;x' data-expect='' data-desc='trailing U+0001'>
+<img srcset='data:,a 1&nbsp;x' data-expect='' data-desc='trailing U+00A0'>
+<img srcset='data:,a 1&#x1680;x' data-expect='' data-desc='trailing U+1680'>
+<img srcset='data:,a 1&#x2000;x' data-expect='' data-desc='trailing U+2000'>
+<img srcset='data:,a 1&#x2001;x' data-expect='' data-desc='trailing U+2001'>
+<img srcset='data:,a 1&#x2002;x' data-expect='' data-desc='trailing U+2002'>
+<img srcset='data:,a 1&#x2003;x' data-expect='' data-desc='trailing U+2003'>
+<img srcset='data:,a 1&#x2004;x' data-expect='' data-desc='trailing U+2004'>
+<img srcset='data:,a 1&#x2005;x' data-expect='' data-desc='trailing U+2005'>
+<img srcset='data:,a 1&#x2006;x' data-expect='' data-desc='trailing U+2006'>
+<img srcset='data:,a 1&#x2007;x' data-expect='' data-desc='trailing U+2007'>
+<img srcset='data:,a 1&#x2008;x' data-expect='' data-desc='trailing U+2008'>
+<img srcset='data:,a 1&#x2009;x' data-expect='' data-desc='trailing U+2009'>
+<img srcset='data:,a 1&#x200A;x' data-expect='' data-desc='trailing U+200A'>
+<img srcset='data:,a 1&#x200C;x' data-expect='' data-desc='trailing U+200C'>
+<img srcset='data:,a 1&#x200D;x' data-expect='' data-desc='trailing U+200D'>
+<img srcset='data:,a 1&#x202F;x' data-expect='' data-desc='trailing U+202F'>
+<img srcset='data:,a 1&#x205F;x' data-expect='' data-desc='trailing U+205F'>
+<img srcset='data:,a 1&#x3000;x' data-expect='' data-desc='trailing U+3000'>
+<img srcset='data:,a 1&#xFEFF;x' data-expect='' data-desc='trailing U+FEFF'>
+<img srcset='data:,a &#x1;1x' data-expect='' data-desc='leading U+0001'>
+<img srcset='data:,a &nbsp;1x' data-expect='' data-desc='leading U+00A0'>
+<img srcset='data:,a &#x1680;1x' data-expect='' data-desc='leading U+1680'>
+<img srcset='data:,a &#x2000;1x' data-expect='' data-desc='leading U+2000'>
+<img srcset='data:,a &#x2001;1x' data-expect='' data-desc='leading U+2001'>
+<img srcset='data:,a &#x2002;1x' data-expect='' data-desc='leading U+2002'>
+<img srcset='data:,a &#x2003;1x' data-expect='' data-desc='leading U+2003'>
+<img srcset='data:,a &#x2004;1x' data-expect='' data-desc='leading U+2004'>
+<img srcset='data:,a &#x2005;1x' data-expect='' data-desc='leading U+2005'>
+<img srcset='data:,a &#x2006;1x' data-expect='' data-desc='leading U+2006'>
+<img srcset='data:,a &#x2007;1x' data-expect='' data-desc='leading U+2007'>
+<img srcset='data:,a &#x2008;1x' data-expect='' data-desc='leading U+2008'>
+<img srcset='data:,a &#x2009;1x' data-expect='' data-desc='leading U+2009'>
+<img srcset='data:,a &#x200A;1x' data-expect='' data-desc='leading U+200A'>
+<img srcset='data:,a &#x200C;1x' data-expect='' data-desc='leading U+200C'>
+<img srcset='data:,a &#x200D;1x' data-expect='' data-desc='leading U+200D'>
+<img srcset='data:,a &#x202F;1x' data-expect='' data-desc='leading U+202F'>
+<img srcset='data:,a &#x205F;1x' data-expect='' data-desc='leading U+205F'>
+<img srcset='data:,a &#x3000;1x' data-expect='' data-desc='leading U+3000'>
+<img srcset='data:,a &#xFEFF;1x' data-expect='' data-desc='leading U+FEFF'>
+<img srcset='data:,a 1w 0h' data-expect=''>
+<img srcset='data:,a 1w -1h' data-expect=''>
+<img srcset='data:,a 1w 1.0h' data-expect=''>
+<img srcset='data:,a 1w 1e0h' data-expect=''>
+<img srcset='data:,a 1w 1hhh' data-expect=''>
+<img srcset='data:,a 1w +1h' data-expect=''>
+<img srcset='data:,a 1w 1H' data-expect=''>
+<img srcset='data:,a 1w Infinityh' data-expect=''>
+<img srcset='data:,a 1w NaNh' data-expect=''>
+<img srcset='data:,a 0x1h' data-expect=''>
+<img srcset='data:,a 0X1h' data-expect=''>
+<img srcset='data:,a 1w 1&#x1;h' data-expect='' data-desc='trailing U+0001'>
+<img srcset='data:,a 1w 1&nbsp;h' data-expect='' data-desc='trailing U+00A0'>
+<img srcset='data:,a 1w 1&#x1680;h' data-expect='' data-desc='trailing U+1680'>
+<img srcset='data:,a 1w 1&#x2000;h' data-expect='' data-desc='trailing U+2000'>
+<img srcset='data:,a 1w 1&#x2001;h' data-expect='' data-desc='trailing U+2001'>
+<img srcset='data:,a 1w 1&#x2002;h' data-expect='' data-desc='trailing U+2002'>
+<img srcset='data:,a 1w 1&#x2003;h' data-expect='' data-desc='trailing U+2003'>
+<img srcset='data:,a 1w 1&#x2004;h' data-expect='' data-desc='trailing U+2004'>
+<img srcset='data:,a 1w 1&#x2005;h' data-expect='' data-desc='trailing U+2005'>
+<img srcset='data:,a 1w 1&#x2006;h' data-expect='' data-desc='trailing U+2006'>
+<img srcset='data:,a 1w 1&#x2007;h' data-expect='' data-desc='trailing U+2007'>
+<img srcset='data:,a 1w 1&#x2008;h' data-expect='' data-desc='trailing U+2008'>
+<img srcset='data:,a 1w 1&#x2009;h' data-expect='' data-desc='trailing U+2009'>
+<img srcset='data:,a 1w 1&#x200A;h' data-expect='' data-desc='trailing U+200A'>
+<img srcset='data:,a 1w 1&#x200C;h' data-expect='' data-desc='trailing U+200C'>
+<img srcset='data:,a 1w 1&#x200D;h' data-expect='' data-desc='trailing U+200D'>
+<img srcset='data:,a 1w 1&#x202F;h' data-expect='' data-desc='trailing U+202F'>
+<img srcset='data:,a 1w 1&#x205F;h' data-expect='' data-desc='trailing U+205F'>
+<img srcset='data:,a 1w 1&#x3000;h' data-expect='' data-desc='trailing U+3000'>
+<img srcset='data:,a 1w 1&#xFEFF;h' data-expect='' data-desc='trailing U+FEFF'>
+<img srcset='data:,a 1w &#x1;1h' data-expect='' data-desc='leading U+0001'>
+<img srcset='data:,a 1w &nbsp;1h' data-expect='' data-desc='leading U+00A0'>
+<img srcset='data:,a 1w &#x1680;1h' data-expect='' data-desc='leading U+1680'>
+<img srcset='data:,a 1w &#x2000;1h' data-expect='' data-desc='leading U+2000'>
+<img srcset='data:,a 1w &#x2001;1h' data-expect='' data-desc='leading U+2001'>
+<img srcset='data:,a 1w &#x2002;1h' data-expect='' data-desc='leading U+2002'>
+<img srcset='data:,a 1w &#x2003;1h' data-expect='' data-desc='leading U+2003'>
+<img srcset='data:,a 1w &#x2004;1h' data-expect='' data-desc='leading U+2004'>
+<img srcset='data:,a 1w &#x2005;1h' data-expect='' data-desc='leading U+2005'>
+<img srcset='data:,a 1w &#x2006;1h' data-expect='' data-desc='leading U+2006'>
+<img srcset='data:,a 1w &#x2007;1h' data-expect='' data-desc='leading U+2007'>
+<img srcset='data:,a 1w &#x2008;1h' data-expect='' data-desc='leading U+2008'>
+<img srcset='data:,a 1w &#x2009;1h' data-expect='' data-desc='leading U+2009'>
+<img srcset='data:,a 1w &#x200A;1h' data-expect='' data-desc='leading U+200A'>
+<img srcset='data:,a 1w &#x200C;1h' data-expect='' data-desc='leading U+200C'>
+<img srcset='data:,a 1w &#x200D;1h' data-expect='' data-desc='leading U+200D'>
+<img srcset='data:,a 1w &#x202F;1h' data-expect='' data-desc='leading U+202F'>
+<img srcset='data:,a 1w &#x205F;1h' data-expect='' data-desc='leading U+205F'>
+<img srcset='data:,a 1w &#x3000;1h' data-expect='' data-desc='leading U+3000'>
+<img srcset='data:,a 1w &#xFEFF;1h' data-expect='' data-desc='leading U+FEFF'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png
new file mode 100644
index 0000000000..d26878c9f2
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png
Binary files differ
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers
new file mode 100644
index 0000000000..edaec7ad15
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/image.png.headers
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html
new file mode 100644
index 0000000000..6fb6847a66
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/resources/resized.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<img srcset="image.png?400 400w, image.png?800 800w, image.png?1600 1600w" sizes="50vw">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html
new file mode 100644
index 0000000000..292395d3ae
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/select-an-image-source.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>img select an image source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=common.js></script>
+<div id=log></div>
+<!-- dup entries -->
+<img srcset='data:,a 1x, data:,b 1x' data-expect='data:,a'>
+<img srcset='data:,a , data:,b 1x' data-expect='data:,a'>
+<img srcset='data:,a 1x, data:,b' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 1w' data-expect='data:,a'>
+<img srcset='data:,a 1w 1h, data:,b 1w' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 1w 1h' data-expect='data:,a'>
+<img srcset='data:,a 1w 1h, data:,b 1w 2h' data-expect='data:,a'>
+<img srcset='data:,a 1w 2h, data:,b 1w 1h' data-expect='data:,a'>
+<img srcset='data:,a , data:,b' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 1x' sizes='1px' data-expect='data:,a'>
+<img srcset='data:,a 1x, data:,b 1w' sizes='1px' data-expect='data:,a'>
+<img srcset='data:,a 1w, data:,b 2x' sizes='0.5px' data-expect='data:,a'>
+<img srcset='data:,a 2x, data:,b 1w' sizes='0.5px' data-expect='data:,a'>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html
new file mode 100644
index 0000000000..2cc74e2b8f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/srcset/srcset-media-dynamic.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>source element in picture handles dynamic media change correctly.</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1523627">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<picture id="pic">
+ <source srcset="data:,a">
+</picture>
+<script>
+let t = async_test("Dynamic media change is handled correctly");
+
+let pic = document.getElementById("pic");
+// Something that will never match.
+pic.querySelector("source").setAttribute("media", "not all");
+
+let img = document.createElement("img");
+img.src = "data:,b";
+pic.appendChild(img);
+
+onload = t.step_func_done(function() {
+ assert_equals(img.currentSrc, "data:,b");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg
new file mode 100644
index 0000000000..fd2eda7164
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/external-sheet.svg
@@ -0,0 +1,4 @@
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+ <style xmlns="http://www.w3.org/1999/xhtml">:root { background-color: green }</style>
+ <link xmlns="http://www.w3.org/1999/xhtml" rel="stylesheet" type="text/css" href="red-bg.css" />
+</svg>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css
new file mode 100644
index 0000000000..da9af10628
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/red-bg.css
@@ -0,0 +1,2 @@
+:root { background: red }
+
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js
new file mode 100644
index 0000000000..7105b03708
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/support/relevant-mutations.js
@@ -0,0 +1,15 @@
+setup({explicit_done:true});
+
+function t(desc, func, expect) {
+ async_test(function() {
+ var img = document.querySelector('[data-desc="' + desc + '"]');
+ img.onload = img.onerror = this.unreached_func('update the image data was run');
+ if (expect == 'timeout') {
+ setTimeout(this.step_func_done(), 1000);
+ } else {
+ img['on' + expect] = this.step_func_done();
+ setTimeout(this.unreached_func('update the image data didn\'t run'), 1000);
+ }
+ func.call(this, img);
+ }, desc);
+}
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html
new file mode 100644
index 0000000000..fdab582933
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>Test reference</title>
+<p>You should see a green square below.</p>
+<div style="background:green;width:100px;height:100px"></div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html
new file mode 100644
index 0000000000..a09dd7cc54
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/svg-img-with-external-stylesheet.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<title>An img element with an svg src should not load external resources from the svg file.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element">
+<link rel="match" href="svg-img-with-external-stylesheet-ref.html">
+<p>You should see a green square below.</p>
+<img width="100" height="100" src="support/external-sheet.svg">
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html
new file mode 100644
index 0000000000..dd679ef571
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-media.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>img update media</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ var t = async_test('set media after src updates selected image');
+
+ var img;
+
+ onload = t.step_func(function() {
+ img = document.querySelector('img');
+ img.addEventListener('load', t.step_func_done(onImgLoad));
+
+ var source = document.querySelector('source[data-media]');
+ source.setAttribute('media', source.getAttribute('data-media'));
+ });
+
+ function onImgLoad() {
+ img.removeEventListener('load', onImgLoad);
+
+ assert_true(img.currentSrc.indexOf(img.getAttribute('data-expect')) > -1);
+ }
+
+</script>
+
+<div id="log"></div>
+<picture>
+ <source srcset="/images/fail.gif" data-media="(max-width: 1px)" />
+ <source srcset="/images/smiley.png" />
+ <img data-expect="/images/smiley.png">
+</picture> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html
new file mode 100644
index 0000000000..de3926a296
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-src-complete.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Changing the img src should retain the 'complete' property</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p id="display"><img src="image.png"></p>
+<script>
+ setup({ single_test: true });
+
+ function check() {
+ var img = document.querySelector("img");
+ assert_true(img.complete, "By onload, image should have loaded");
+ img.src = `image.png?${Math.random()}`;
+ assert_false(img.complete, "Now that we're loading we should no longer be complete");
+ img.onload = function () {
+ assert_true(img.complete, "The new thing should have loaded.");
+ done();
+ }
+ }
+
+ onload = function () {
+ check();
+ };
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html
new file mode 100644
index 0000000000..125b37eadb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/current-request-microtask.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>An img's current request should be updated in a microtask after selecting an image source</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<script>
+async_test(function(t) {
+ const picture = document.createElement("picture");
+
+ const nonMatchingSource = document.createElement("source");
+ nonMatchingSource.media = "not all";
+ nonMatchingSource.srcset = "data:,a";
+ picture.append(nonMatchingSource);
+
+ const matchingSource = document.createElement("source");
+ matchingSource.media = "all";
+ matchingSource.srcset = "data:,b";
+ picture.append(matchingSource);
+
+ const img = document.createElement("img");
+ img.src = "data:,c";
+
+ assert_equals(img.currentSrc, "", "after assigning to img.src but before the corresponding microtask is run");
+
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, "data:,c", "after assigning to img.src and after corresponding microtask is run");
+
+ picture.append(img);
+ assert_equals(img.currentSrc, "data:,c", "after appending img to picture but before the corresponding microtask is run");
+
+ queueMicrotask(t.step_func(function() {
+ assert_equals(img.currentSrc, "data:,b", "after appending img to picture and after the corresponding microtask is run");
+ t.done();
+ }));
+ }));
+}, "currentSrc is updated only after the microtask that updates the current request is run");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html
new file mode 100644
index 0000000000..959ceaa979
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-image-data/fail-to-resolve.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>img update the image data: fail to resolve URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<img src="//[">
+<img srcset="//[">
+<img srcset="//[" src="/images/red.png">
+<img srcset="//[, /images/red.png">
+
+<script>
+setup({explicit_done: true});
+
+var expected = '//[';
+
+onload = function() {
+ [].forEach.call(document.images, function(img) {
+ test(function() {
+ assert_equals(img.currentSrc, expected);
+ }, img.outerHTML);
+ });
+ done();
+};
+</script>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html
new file mode 100644
index 0000000000..063667baa9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/update-the-source-set.html
@@ -0,0 +1,140 @@
+<!doctype html>
+<title>img update the source set</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({explicit_done:true});
+
+function check(p) {
+ var img = p.querySelector('[data-expect]');
+ test(function() {
+ var expect = img.dataset.expect;
+ if ('resolve' in img.dataset) {
+ var a = document.createElement('a');
+ a.href = expect;
+ expect = a.href;
+ }
+ assert_equals(img.currentSrc, expect);
+ }, p.innerHTML);
+}
+
+onload = function() {
+ [].forEach.call(document.querySelectorAll('div:not([id])'), check);
+ done();
+};
+
+</script>
+<div id=log></div>
+<div><img data-expect=''></div>
+<div><img src data-expect=''></div>
+<div><img src='data:,a' data-expect='data:,a'></div>
+<div><img srcset src='data:,a' data-expect='data:,a'></div>
+<div><img srcset='data:,b' src='data:,a' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b' data-expect='data:,b'><!-- srcset after src --></div>
+<div><img src='data:,a' srcset='data:,b 1x' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1.0x' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1e0x' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 10000w' sizes='1px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 10000w, data:,c 10000x' sizes='1px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 10000x, data:,c 10000w' sizes='1px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1w' sizes='10000px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 1w, data:,c 0.0001x' sizes='10000px' data-expect='data:,b'></div>
+<div><img src='data:,a' srcset='data:,b 0.0001x, data:,c 1w' sizes='10000px' data-expect='data:,b'></div>
+<div><img srcset='data:,a' data-expect='data:,a'></div>
+
+<!-- child is not a <source> -->
+
+<div><picture>foo<img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><!--foo--><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><br><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><p></p><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><video><source srcset='data:,b'></video><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><span><source srcset='data:,b'></span><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><svg><source srcset='data:,b'/></svg><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><svg/><source srcset='data:,b'/><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><svg><font/><source srcset='data:,b'/></svg><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><svg><!--<font face> tag breaks out of svg--><font face></font><source srcset='data:,b'/></svg><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><img src='data:,a'><img src='data:,b' data-expect='data:,b'></picture></div>
+
+<!-- <source> has no srcset -->
+
+<div><picture><source><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source src='data:,b'><img src='data:,a' data-expect='data:,a'></picture></div>
+
+<!-- <source srcset> has zero candidates -->
+
+<div><picture><source srcset><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset=', ,'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b 1x 1x'><img src='data:,a' data-expect='data:,a'></picture></div>
+
+<!-- <source media> -->
+
+<div><picture><source srcset='data:,b' media><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='all'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='all and (min-width:0)'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='all and !'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='all and (!)'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and (min-width:0)'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and (max-width:0)'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and !'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='not all and (!)'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media='all, !'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' media=','><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' media=', all'><img src='data:,a' data-expect='data:,b'></picture></div>
+
+<!-- <source type> assume support for gif, png, jpg, svg, ico -->
+
+<div><picture><source srcset='data:,b' type><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type=' '><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type=' image/gif'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif '><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;encodings'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;encodings='><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif;encodings=foobar'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/png'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/jpeg'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/svg+xml'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='image/x-icon'><img src='data:,a' data-expect='data:,b'></picture></div>
+<div><picture><source srcset='data:,b' type='text/xml'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='text/html'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='text/plain'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='text/css'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='video/mp4'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='video/ogg'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='video/webm'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='unknown/unknown'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='application/octet-stream'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='application/x-shockwave-flash'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image\gif'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='gif'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='.gif'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='*'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='*/*'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/*'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type=','><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif, image/png'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/gif image/png'><img src='data:,a' data-expect='data:,a'></picture></div>
+<div><picture><source srcset='data:,b' type='image/foobarbaz'><img src='data:,a' data-expect='data:,a'></picture></div>
+
+<!-- trailing garbage -->
+
+<div><picture><img src='data:,a' data-expect='data:,a'>foo</picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><br></picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><!--foo--></picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><img src='data:,b'></picture></div>
+<div><picture><img data-expect=''><img src='data:,b'></picture></div>
+<div><picture><img src='data:,a' data-expect='data:,a'><source srcset='data:,b'></picture></div>
+<div><picture><img data-expect=''><source srcset='data:,b'></picture></div>
+
+<!-- parent not picture -->
+
+<div><picture><span><source srcset='data:,b'><img data-expect=''></span></picture></div>
+<div><picture><span><source srcset='data:,b'><img src='data:,a' data-expect='data:,a'></span></picture></div>
+<div><picture><source srcset='data:,b'><span><img src='data:,a' data-expect='data:,a'></span></picture></div>
+
+<!-- no src -->
+
+<div><picture><source srcset='data:,b'><img data-expect='data:,b'></picture></div>
diff --git a/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html
new file mode 100644
index 0000000000..c28f667ff3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/embedded-content/the-img-element/usemap-casing.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>img usemap case-sensitive</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-a-hash-name-reference">
+<!-- See also: https://github.com/whatwg/html/issues/1666 -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<img src="/images/threecolors.png" usemap="#sanityCheck" width="100" height="100">
+<map name="sanityCheck"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#sImPlE" width="100" height="100">
+<map name="simple"><area shape="rect" coords="0,0,100,100"></map>
+<map name="SIMPLE"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#paSSfield-killroyß" width="100" height="100">
+<map name="passfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="PASSFIELD-KILLROYß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="paſſfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfield-&#x212a;illroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="paßfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="paẞfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfield-killroyẞ"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfield-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfıeld-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+<map name="passfİeld-killroyß"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#глупый" width="100" height="100">
+<map name="глупы&#x438;&#x306;"><area shape="rect" coords="0,0,100,100"></map>
+<map name="ГЛУПЫЙ"><area shape="rect" coords="0,0,100,100"></map>
+<map name="ГЛУПЫ&#x418;&#x306;"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#åωk" width="100" height="100">
+<map name="ÅΩK"><area shape="rect" coords="0,0,100,100"></map>
+<map name="&#x212b;ωk"><area shape="rect" coords="0,0,100,100"></map>
+<map name="å&#x2126;k"><area shape="rect" coords="0,0,100,100"></map>
+<map name="åω&#x212a;"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#blah1" width="100" height="100">
+<map name="blah&#x2460;"><area shape="rect" coords="0,0,100,100"></map>
+<map name="bl&#x24b6;h1"><area shape="rect" coords="0,0,100,100"></map>
+<map name="bl&#x24d0;h1"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#t&Eacute;dz5アパートFi" width="100" height="100">
+<map name="T&Eacute;DZ5アパートFi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="T&eacute;&#x01F1;&#x2075;アパートFi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="t&Eacute;dz5&#x3100;Fi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="t&Eacute;dz5&#x30A2;&#x30CF;&#x309A;&#x30FC;&#x30C8;Fi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="T&Eacute;DZ⁵アパートFi"><area shape="rect" coords="0,0,100,100"></map>
+<map name="T&Eacute;DZ5アパートfi"><area shape="rect" coords="0,0,100,100"></map>
+
+<img src="/images/threecolors.png" usemap="#ΣΣ" width="100" height="100">
+<map name="σς"><area shape="rect" coords="0,0,100,100"></map>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+setup({ explicit_done: true });
+
+onload = () => {
+ test(() => {
+ const image = document.querySelector(`img[usemap="#sanityCheck"]`);
+ const imageRect = image.getBoundingClientRect();
+ const x = imageRect.left + imageRect.width / 2;
+ const y = imageRect.top + imageRect.height / 2;
+ const element = document.elementFromPoint(x, y);
+ const area = document.querySelector(`map[name="sanityCheck"] > area`);
+
+ assert_equals(element, area);
+ }, `Image with usemap of #sanityCheck should match the area with map named sanityCheck`);
+
+ const images = Array.from(document.querySelectorAll(`img:not([usemap="#sanityCheck"])`));
+
+ for (let image of images) {
+ test(() => {
+ const imageRect = image.getBoundingClientRect();
+ const x = imageRect.left + imageRect.width / 2;
+ const y = imageRect.top + imageRect.height / 2;
+ const element = document.elementFromPoint(x, y);
+
+ const name = element.parentElement.getAttribute("name");
+ const messageSuffix = name ? `; used <map> with name "${name}"` : "";
+
+ assert_equals(element, image, "The element retrieved must be the image, not an area" + messageSuffix);
+ }, `Image with usemap of ${image.useMap} should not match any of the areas`);
+ }
+
+ done();
+};
+</script>