Skip to content

Commit fdd3975

Browse files
tcl3awesomekling
authored andcommitted
LibWeb: Don't run update_the_image_data() algorithm if already started
This ensures that events are not fired if the image source is updated while it is being fetched.
1 parent 66263f1 commit fdd3975

File tree

8 files changed

+251
-23
lines changed

8 files changed

+251
-23
lines changed

Libraries/LibWeb/HTML/HTMLImageElement.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ static BatchingDispatcher& batching_dispatcher()
499499
void HTMLImageElement::update_the_image_data(bool restart_animations, bool maybe_omit_events)
500500
{
501501
auto& realm = this->realm();
502+
auto update_the_image_data_count = ++m_update_the_image_data_count;
502503

503504
// 1. If the element's node document is not fully active, then:
504505
if (!document().is_fully_active()) {
@@ -510,21 +511,21 @@ void HTMLImageElement::update_the_image_data(bool restart_animations, bool maybe
510511
return;
511512

512513
m_document_observer = realm.create<DOM::DocumentObserver>(realm, document());
513-
m_document_observer->set_document_became_active([this, restart_animations, maybe_omit_events]() {
514+
m_document_observer->set_document_became_active([this, restart_animations, maybe_omit_events, update_the_image_data_count]() {
514515
// 4. Queue a microtask to continue this algorithm.
515-
queue_a_microtask(&document(), GC::create_function(this->heap(), [this, restart_animations, maybe_omit_events]() {
516-
update_the_image_data_impl(restart_animations, maybe_omit_events);
516+
queue_a_microtask(&document(), GC::create_function(this->heap(), [this, restart_animations, maybe_omit_events, update_the_image_data_count]() {
517+
update_the_image_data_impl(restart_animations, maybe_omit_events, update_the_image_data_count);
517518
}));
518519
});
519520

520521
return;
521522
}
522523

523-
update_the_image_data_impl(restart_animations, maybe_omit_events);
524+
update_the_image_data_impl(restart_animations, maybe_omit_events, update_the_image_data_count);
524525
}
525526

526527
// https://html.spec.whatwg.org/multipage/images.html#update-the-image-data
527-
void HTMLImageElement::update_the_image_data_impl(bool restart_animations, bool maybe_omit_events)
528+
void HTMLImageElement::update_the_image_data_impl(bool restart_animations, bool maybe_omit_events, u64 update_the_image_data_count)
528529
{
529530
// 1. If the element's node document is not fully active, then:
530531
// FIXME: This step and it's substeps is implemented by the calling `update_the_image_data` function.
@@ -621,9 +622,11 @@ void HTMLImageElement::update_the_image_data_impl(bool restart_animations, bool
621622
}
622623
after_step_7:
623624
// 8. Queue a microtask to perform the rest of this algorithm, allowing the task that invoked this algorithm to continue.
624-
queue_a_microtask(&document(), GC::create_function(this->heap(), [this, restart_animations, maybe_omit_events, previous_url]() mutable {
625-
// FIXME: 9. If another instance of this algorithm for this img element was started after this instance
626-
// (even if it aborted and is no longer running), then return.
625+
queue_a_microtask(&document(), GC::create_function(this->heap(), [this, update_the_image_data_count, restart_animations, maybe_omit_events, previous_url]() mutable {
626+
// 9. If another instance of this algorithm for this img element was started after this instance
627+
// (even if it aborted and is no longer running), then return.
628+
if (update_the_image_data_count != m_update_the_image_data_count)
629+
return;
627630

628631
// 10. Let selected source and selected pixel density be
629632
// the URL and pixel density that results from selecting an image source, respectively.

Libraries/LibWeb/HTML/HTMLImageElement.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class HTMLImageElement final
115115
private:
116116
HTMLImageElement(DOM::Document&, DOM::QualifiedName);
117117

118-
void update_the_image_data_impl(bool restart_the_animations = false, bool maybe_omit_events = false);
118+
void update_the_image_data_impl(bool restart_the_animations, bool maybe_omit_events, u64 update_the_image_data_count);
119119

120120
virtual bool is_html_image_element() const override { return true; }
121121

@@ -164,6 +164,8 @@ class HTMLImageElement final
164164
SourceSet m_source_set;
165165

166166
CSSPixelSize m_last_seen_viewport_size;
167+
168+
u64 m_update_the_image_data_count { 0 };
167169
};
168170

169171
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Harness status: OK
2+
3+
Found 17 tests
4+
5+
14 Pass
6+
3 Fail
7+
Pass img src and srcset omitted
8+
Pass img src and srcset omitted on newly-created image
9+
Pass img src and srcset omitted on newly-created-and-inserted image
10+
Pass img src and srcset omitted on newly-created-via-innerHTML image
11+
Pass img src empty and srcset omitted
12+
Pass img src empty and srcset omitted on newly-created image
13+
Pass img src empty and srcset omitted on newly-created-and-inserted image
14+
Pass img src empty and srcset omitted on newly-created-via-innerHTML image
15+
Pass img src and srcset omitted on image after it started a load
16+
Pass async src complete test
17+
Fail async srcset complete test
18+
Pass IDL attribute complete cannot "randomly" change during a task
19+
Pass async src broken test
20+
Pass async src removal test
21+
Fail async srcset removal test
22+
Pass async src available image lookup test
23+
Fail async pending request test

Tests/LibWeb/Text/expected/wpt-import/html/semantics/embedded-content/the-img-element/naturalWidth-naturalHeight-width-height.tentative.txt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ Harness status: OK
22

33
Found 228 tests
44

5-
52 Pass
6-
176 Fail
5+
56 Pass
6+
172 Fail
77
Pass raster image
88
Pass raster image (when not rendered)
99
Pass raster image with width/height attributes
@@ -124,11 +124,11 @@ Fail SVG image, with natural height, and aspect ratio from viewBox (with srcset/
124124
Fail SVG image, with natural height, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
125125
Pass SVG image, with natural width of 0, and aspect ratio from viewBox (with srcset/1x)
126126
Pass SVG image, with natural width of 0, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
127-
Fail SVG image, with natural height of 0, and aspect ratio from viewBox (with srcset/1x)
127+
Pass SVG image, with natural height of 0, and aspect ratio from viewBox (with srcset/1x)
128128
Pass SVG image, with natural height of 0, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
129-
Fail SVG image, with natural width being negative, and aspect ratio from viewBox (with srcset/1x)
129+
Pass SVG image, with natural width being negative, and aspect ratio from viewBox (with srcset/1x)
130130
Pass SVG image, with natural width being negative, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
131-
Fail SVG image, with natural height being negative, and aspect ratio from viewBox (with srcset/1x)
131+
Pass SVG image, with natural height being negative, and aspect ratio from viewBox (with srcset/1x)
132132
Pass SVG image, with natural height being negative, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
133133
Fail SVG image, no natural dimensions, viewBox with 0 width/height (with srcset/1x)
134134
Fail SVG image, no natural dimensions, viewBox with 0 width/height (with srcset/1x) (when not rendered)
@@ -148,13 +148,13 @@ Fail SVG image, with natural height, viewBox with 0 width (with srcset/1x)
148148
Fail SVG image, with natural height, viewBox with 0 width (with srcset/1x) (when not rendered)
149149
Fail SVG image, with natural height, viewBox with 0 height (with srcset/1x)
150150
Fail SVG image, with natural height, viewBox with 0 height (with srcset/1x) (when not rendered)
151-
Pass SVG image, with natural width and height (with srcset/1x)
152-
Pass SVG image, with natural width and height (with srcset/1x) (when not rendered)
153-
Pass SVG image, with natural width and height, and aspect ratio from viewBox (with srcset/1x)
154-
Pass SVG image, with natural width and height, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
151+
Fail SVG image, with natural width and height (with srcset/1x)
152+
Fail SVG image, with natural width and height (with srcset/1x) (when not rendered)
153+
Fail SVG image, with natural width and height, and aspect ratio from viewBox (with srcset/1x)
154+
Fail SVG image, with natural width and height, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
155155
Pass SVG image, with natural width and height of 0, and aspect ratio from viewBox (with srcset/1x)
156156
Pass SVG image, with natural width and height of 0, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
157-
Fail SVG image, with natural width and height being negative, and aspect ratio from viewBox (with srcset/1x)
157+
Pass SVG image, with natural width and height being negative, and aspect ratio from viewBox (with srcset/1x)
158158
Pass SVG image, with natural width and height being negative, and aspect ratio from viewBox (with srcset/1x) (when not rendered)
159159
Fail raster image (with srcset/2x)
160160
Fail raster image (with srcset/2x) (when not rendered)
@@ -200,11 +200,11 @@ Fail SVG image, with natural height, and aspect ratio from viewBox (with srcset/
200200
Fail SVG image, with natural height, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
201201
Pass SVG image, with natural width of 0, and aspect ratio from viewBox (with srcset/2x)
202202
Pass SVG image, with natural width of 0, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
203-
Fail SVG image, with natural height of 0, and aspect ratio from viewBox (with srcset/2x)
203+
Pass SVG image, with natural height of 0, and aspect ratio from viewBox (with srcset/2x)
204204
Pass SVG image, with natural height of 0, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
205-
Fail SVG image, with natural width being negative, and aspect ratio from viewBox (with srcset/2x)
205+
Pass SVG image, with natural width being negative, and aspect ratio from viewBox (with srcset/2x)
206206
Pass SVG image, with natural width being negative, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
207-
Fail SVG image, with natural height being negative, and aspect ratio from viewBox (with srcset/2x)
207+
Pass SVG image, with natural height being negative, and aspect ratio from viewBox (with srcset/2x)
208208
Pass SVG image, with natural height being negative, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
209209
Fail SVG image, no natural dimensions, viewBox with 0 width/height (with srcset/2x)
210210
Fail SVG image, no natural dimensions, viewBox with 0 width/height (with srcset/2x) (when not rendered)
@@ -230,5 +230,5 @@ Fail SVG image, with natural width and height, and aspect ratio from viewBox (wi
230230
Fail SVG image, with natural width and height, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
231231
Pass SVG image, with natural width and height of 0, and aspect ratio from viewBox (with srcset/2x)
232232
Pass SVG image, with natural width and height of 0, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
233-
Fail SVG image, with natural width and height being negative, and aspect ratio from viewBox (with srcset/2x)
233+
Pass SVG image, with natural width and height being negative, and aspect ratio from viewBox (with srcset/2x)
234234
Pass SVG image, with natural width and height being negative, and aspect ratio from viewBox (with srcset/2x) (when not rendered)
88.9 KB
Loading
380 KB
Loading
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
<!DOCTYPE HTML>
2+
<title>DOM img complete Test</title>
3+
<meta charset=UTF-8>
4+
<link rel="author" title="Anselm Hannemann" href="http://anselm-hannemann.com/" />
5+
<script src="../../../../resources/testharness.js"></script>
6+
<script src="../../../../resources/testharnessreport.js"></script>
7+
8+
<img id="imgTestTag">
9+
<img src="" id="imgTestTag2">
10+
<img id="imgTestTag3" style="width: 80px; height:auto;">
11+
<img id="imgTestTag4">
12+
<img id="imgTestTag5">
13+
<div id="image-container"></div>
14+
15+
<script>
16+
var imageInstance = document.createElement('img');
17+
imageInstance.style.display = 'none';
18+
19+
document.body.appendChild(imageInstance);
20+
</script>
21+
22+
<div id="log"></div>
23+
<script>
24+
test(function() {
25+
assert_true(document.getElementById("imgTestTag").complete);
26+
}, "img src and srcset omitted");
27+
28+
test(function() {
29+
var img = document.createElement("img");
30+
assert_true(img.complete);
31+
}, "img src and srcset omitted on newly-created image");
32+
33+
test(function() {
34+
var cont = document.getElementById("image-container");
35+
this.add_cleanup(() => { cont.innerHTML = "" });
36+
var img = document.createElement("img");
37+
cont.appendChild(img);
38+
assert_true(img.complete);
39+
}, "img src and srcset omitted on newly-created-and-inserted image");
40+
41+
test(function() {
42+
var cont = document.getElementById("image-container");
43+
this.add_cleanup(() => { cont.innerHTML = "" });
44+
cont.innerHTML = "<img>";
45+
assert_true(cont.querySelector("img").complete);
46+
}, "img src and srcset omitted on newly-created-via-innerHTML image");
47+
48+
test(function() {
49+
assert_true(document.getElementById("imgTestTag2").complete);
50+
}, "img src empty and srcset omitted");
51+
52+
test(function() {
53+
var img = document.createElement("img");
54+
img.setAttribute("src", "");
55+
assert_true(img.complete);
56+
}, "img src empty and srcset omitted on newly-created image");
57+
58+
test(function() {
59+
var cont = document.getElementById("image-container");
60+
this.add_cleanup(() => { cont.innerHTML = "" });
61+
var img = document.createElement("img");
62+
img.setAttribute("src", "");
63+
cont.appendChild(img);
64+
assert_true(img.complete);
65+
}, "img src empty and srcset omitted on newly-created-and-inserted image");
66+
67+
test(function() {
68+
var cont = document.getElementById("image-container");
69+
this.add_cleanup(() => { cont.innerHTML = "" });
70+
cont.innerHTML = "<img src=''>";
71+
assert_true(cont.querySelector("img").complete);
72+
}, "img src empty and srcset omitted on newly-created-via-innerHTML image");
73+
74+
test(function() {
75+
var img = document.createElement("img");
76+
img.src = location.href;
77+
assert_false(img.complete, "Should have a load going");
78+
img.removeAttribute("src");
79+
assert_true(img.complete);
80+
}, "img src and srcset omitted on image after it started a load");
81+
82+
// test if set to true after img is completely available
83+
async_test(t => {
84+
var loaded = false;
85+
const img = document.getElementById("imgTestTag3");
86+
img.onload = t.step_func_done(function(){
87+
assert_false(loaded);
88+
loaded = true;
89+
assert_true(img.complete);
90+
var currentSrc = img.currentSrc;
91+
var expectedUrl = new URL("3.jpg", window.location);
92+
assert_equals(new URL(currentSrc).pathname, expectedUrl.pathname);
93+
}, "Only one onload, despite setting the src twice");
94+
95+
img.src = 'test' + Math.random();
96+
//test if img.complete is set to false if src is changed
97+
assert_false(img.complete, "src changed, should be set to false")
98+
//change src again, should make only one request as per 'await stable state'
99+
img.src = '3.jpg?nocache=' + Math.random();
100+
}, "async src complete test");
101+
102+
async_test(t => {
103+
var loaded = false;
104+
const img = document.getElementById("imgTestTag5")
105+
img.onload = t.step_func_done(function(){
106+
assert_false(loaded);
107+
loaded = true;
108+
assert_true(img.complete);
109+
}, "Only one onload, despite setting the srcset twice");
110+
//Test if src, srcset is omitted
111+
assert_true(img.complete)
112+
img.srcset = "../../../../images/green-256x256.png 1x";
113+
//test if img.complete is set to false if srcset is present
114+
assert_false(img.complete, "srcset present, should be set to false");
115+
//change src again, should make only one request as per 'await stable state'
116+
img.srcset="../../../../images/green-256x256.png 1.6x"
117+
}, "async srcset complete test");
118+
119+
// https://html.spec.whatwg.org/multipage/multipage/embedded-content-1.html#update-the-image-data
120+
// says to "await a stable state" before fetching so we use a separate <script>
121+
imageInstance.src = 'image-1.jpg?pipe=trickle(d1)&nocache=' + Math.random(); // make sure the image isn't in cache
122+
</script>
123+
<script>
124+
// 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
125+
test(function() {
126+
assert_false(imageInstance.complete, "imageInstance.complete should be false");
127+
var startTime = Date.now();
128+
while (true) {
129+
if (Date.now() - startTime > 2000) {
130+
assert_false(imageInstance.complete, "imageInstance.complete should remain false");
131+
break;
132+
}
133+
if (imageInstance.complete === true) {
134+
assert_unreached(".complete should not change within a task");
135+
}
136+
}
137+
},
138+
'IDL attribute complete cannot "randomly" change during a task');
139+
140+
// test if broken img does not pass
141+
async_test(t => {
142+
const img = document.getElementById("imgTestTag4");
143+
144+
img.src = 'brokenimg.jpg';
145+
146+
//test if img.complete is set to false if src is changed
147+
assert_false(img.complete, "src changed to broken img, should be set to false");
148+
149+
img.onload = img.onerror = t.step_func_done(function(event){
150+
assert_equals(event.type, "error");
151+
assert_true(img.complete);
152+
});
153+
}, "async src broken test");
154+
155+
async_test(t => {
156+
var img = document.createElement("img");
157+
assert_true(img.complete);
158+
img.src = `3.jpg?nocache=${Math.random()}`;
159+
assert_false(img.complete);
160+
img.onload = t.step_func_done(() => {
161+
assert_true(img.complete);
162+
img.removeAttribute("src");
163+
assert_true(img.complete, "Should be complete, since we removed the src");
164+
});
165+
}, "async src removal test");
166+
167+
async_test(t => {
168+
var img = document.createElement("img");
169+
assert_true(img.complete);
170+
img.srcset = `3.jpg?nocache=${Math.random()} 1x`;
171+
assert_false(img.complete);
172+
img.onload = t.step_func_done(() => {
173+
assert_true(img.complete);
174+
img.removeAttribute("srcset");
175+
assert_true(img.complete, "Should be complete, since we removed the srcset");
176+
});
177+
}, "async srcset removal test");
178+
179+
async_test(t => {
180+
var preload = document.createElement("img");
181+
var url = `3.jpg?nocache=${Math.random()}`;
182+
preload.src = url;
183+
preload.onload = t.step_func_done(function() {
184+
var img = document.createElement("img");
185+
assert_true(img.complete);
186+
img.src = url;
187+
assert_true(img.complete, "Should be complete because we should hit the available image cache");
188+
});
189+
}, "async src available image lookup test");
190+
191+
async_test(t => {
192+
var img = document.createElement("img");
193+
img.src = `3.jpg?nocache=${Math.random()}`;
194+
img.onload = t.step_func_done(function() {
195+
assert_true(img.complete);
196+
img.src = `3.jpg?nocache=${Math.random()}`;
197+
assert_false(img.complete, "Should not be complete because we have started a new load");
198+
});
199+
}, "async pending request test");
200+
</script>
103 Bytes
Loading

0 commit comments

Comments
 (0)