mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #15771 - jdm:img-panic, r=nox
Improve behaviour of image elements that perform multiple requests This addresses cases where image elements end up making multiple requests, as well as makes the element respond to additional relevant mutations that trigger updating the image data. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #15709 (github issue number if applicable). - [X] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15771) <!-- Reviewable:end -->
This commit is contained in:
commit
f90fc2fa88
10 changed files with 173 additions and 34 deletions
|
@ -120,7 +120,7 @@ impl DocumentLoader {
|
|||
}
|
||||
|
||||
/// Initiate a new fetch that does not block the document load event.
|
||||
pub fn fetch_async_background(&mut self,
|
||||
pub fn fetch_async_background(&self,
|
||||
request: RequestInit,
|
||||
fetch_target: IpcSender<FetchResponseMsg>) {
|
||||
self.resource_threads.sender().send(CoreResourceMsg::Fetch(request, fetch_target)).unwrap();
|
||||
|
|
|
@ -108,7 +108,7 @@ use net_traits::response::HttpsState;
|
|||
use num_traits::ToPrimitive;
|
||||
use script_layout_interface::message::{Msg, ReflowQueryType};
|
||||
use script_runtime::{CommonScriptMsg, ScriptThreadEventCategory};
|
||||
use script_thread::{MainThreadScriptMsg, Runnable};
|
||||
use script_thread::{MainThreadScriptMsg, Runnable, ScriptThread};
|
||||
use script_traits::{AnimationState, CompositorEvent, DocumentActivity};
|
||||
use script_traits::{MouseButton, MouseEventType, MozBrowserEvent};
|
||||
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TouchpadPressurePhase};
|
||||
|
@ -1641,17 +1641,26 @@ impl Document {
|
|||
// Step 5 can be found in asap_script_loaded and
|
||||
// asap_in_order_script_loaded.
|
||||
|
||||
let loader = self.loader.borrow();
|
||||
if loader.is_blocked() || loader.events_inhibited() {
|
||||
// Step 6.
|
||||
return;
|
||||
}
|
||||
|
||||
ScriptThread::mark_document_with_no_blocked_loads(self);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-end
|
||||
pub fn maybe_queue_document_completion(&self) {
|
||||
if self.loader.borrow().is_blocked() {
|
||||
// Step 6.
|
||||
return;
|
||||
}
|
||||
|
||||
// The rest will ever run only once per document.
|
||||
if self.loader.borrow().events_inhibited() {
|
||||
return;
|
||||
}
|
||||
assert!(!self.loader.borrow().events_inhibited());
|
||||
self.loader.borrow_mut().inhibit_events();
|
||||
|
||||
// The rest will ever run only once per document.
|
||||
// Step 7.
|
||||
debug!("Document loads are complete.");
|
||||
let handler = box DocumentProgressHandler::new(Trusted::new(self));
|
||||
|
|
|
@ -47,6 +47,8 @@ use network_listener::{NetworkListener, PreInvoke};
|
|||
use num_traits::ToPrimitive;
|
||||
use script_thread::Runnable;
|
||||
use servo_url::ServoUrl;
|
||||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
use std::i32;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
||||
|
@ -76,6 +78,7 @@ pub struct HTMLImageElement {
|
|||
htmlelement: HTMLElement,
|
||||
current_request: DOMRefCell<ImageRequest>,
|
||||
pending_request: DOMRefCell<ImageRequest>,
|
||||
generation: Cell<u32>,
|
||||
}
|
||||
|
||||
impl HTMLImageElement {
|
||||
|
@ -87,14 +90,16 @@ impl HTMLImageElement {
|
|||
struct ImageResponseHandlerRunnable {
|
||||
element: Trusted<HTMLImageElement>,
|
||||
image: ImageResponse,
|
||||
generation: u32,
|
||||
}
|
||||
|
||||
impl ImageResponseHandlerRunnable {
|
||||
fn new(element: Trusted<HTMLImageElement>, image: ImageResponse)
|
||||
fn new(element: Trusted<HTMLImageElement>, image: ImageResponse, generation: u32)
|
||||
-> ImageResponseHandlerRunnable {
|
||||
ImageResponseHandlerRunnable {
|
||||
element: element,
|
||||
image: image,
|
||||
generation: generation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,35 +109,29 @@ impl Runnable for ImageResponseHandlerRunnable {
|
|||
|
||||
fn handler(self: Box<Self>) {
|
||||
let element = self.element.root();
|
||||
element.process_image_response(self.image);
|
||||
// Ignore any image response for a previous request that has been discarded.
|
||||
if element.generation.get() == self.generation {
|
||||
element.process_image_response(self.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The context required for asynchronously loading an external image.
|
||||
struct ImageContext {
|
||||
/// The element that initiated the request.
|
||||
elem: Trusted<HTMLImageElement>,
|
||||
/// The initial URL requested.
|
||||
url: ServoUrl,
|
||||
/// A handle with which to communicate with the image cache.
|
||||
image_cache: ImageCacheThread,
|
||||
/// Indicates whether the request failed, and why
|
||||
status: Result<(), NetworkError>,
|
||||
/// The cache ID for this request.
|
||||
id: PendingImageId,
|
||||
}
|
||||
|
||||
impl ImageContext {
|
||||
fn image_cache(&self) -> ImageCacheThread {
|
||||
let elem = self.elem.root();
|
||||
window_from_node(&*elem).image_cache_thread().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchResponseListener for ImageContext {
|
||||
fn process_request_body(&mut self) {}
|
||||
fn process_request_eof(&mut self) {}
|
||||
|
||||
fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) {
|
||||
self.image_cache().notify_pending_response(
|
||||
self.image_cache.notify_pending_response(
|
||||
self.id,
|
||||
FetchResponseMsg::ProcessResponse(metadata.clone()));
|
||||
|
||||
|
@ -156,19 +155,16 @@ impl FetchResponseListener for ImageContext {
|
|||
|
||||
fn process_response_chunk(&mut self, payload: Vec<u8>) {
|
||||
if self.status.is_ok() {
|
||||
self.image_cache().notify_pending_response(
|
||||
self.image_cache.notify_pending_response(
|
||||
self.id,
|
||||
FetchResponseMsg::ProcessResponseChunk(payload));
|
||||
}
|
||||
}
|
||||
|
||||
fn process_response_eof(&mut self, response: Result<(), NetworkError>) {
|
||||
let elem = self.elem.root();
|
||||
let document = document_from_node(&*elem);
|
||||
let image_cache = self.image_cache();
|
||||
image_cache.notify_pending_response(self.id,
|
||||
FetchResponseMsg::ProcessResponseEOF(response));
|
||||
document.finish_load(LoadType::Image(self.url.clone()));
|
||||
self.image_cache.notify_pending_response(
|
||||
self.id,
|
||||
FetchResponseMsg::ProcessResponseEOF(response));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,11 +193,12 @@ impl HTMLImageElement {
|
|||
let window = window_from_node(elem);
|
||||
let task_source = window.networking_task_source();
|
||||
let wrapper = window.get_runnable_wrapper();
|
||||
let generation = elem.generation.get();
|
||||
ROUTER.add_route(responder_receiver.to_opaque(), box move |message| {
|
||||
// Return the image via a message to the script thread, which marks the element
|
||||
// as dirty and triggers a reflow.
|
||||
let runnable = ImageResponseHandlerRunnable::new(
|
||||
trusted_node.clone(), message.to().unwrap());
|
||||
trusted_node.clone(), message.to().unwrap(), generation);
|
||||
let _ = task_source.queue_with_wrapper(box runnable, &wrapper);
|
||||
});
|
||||
|
||||
|
@ -243,8 +240,7 @@ impl HTMLImageElement {
|
|||
let window = window_from_node(self);
|
||||
|
||||
let context = Arc::new(Mutex::new(ImageContext {
|
||||
elem: Trusted::new(self),
|
||||
url: img_url.clone(),
|
||||
image_cache: window.image_cache_thread().clone(),
|
||||
status: Ok(()),
|
||||
id: id,
|
||||
}));
|
||||
|
@ -267,7 +263,9 @@ impl HTMLImageElement {
|
|||
.. RequestInit::default()
|
||||
};
|
||||
|
||||
document.fetch_async(LoadType::Image(img_url), request, action_sender);
|
||||
// This is a background load because the load blocker already fulfills the
|
||||
// purpose of delaying the document's load event.
|
||||
document.loader().fetch_async_background(request, action_sender);
|
||||
}
|
||||
|
||||
fn process_image_response(&self, image: ImageResponse) {
|
||||
|
@ -299,7 +297,9 @@ impl HTMLImageElement {
|
|||
self.upcast::<EventTarget>().fire_event(atom!("error"));
|
||||
}
|
||||
|
||||
LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
|
||||
if trigger_image_load || trigger_image_error {
|
||||
LoadBlocker::terminate(&mut self.current_request.borrow_mut().blocker);
|
||||
}
|
||||
|
||||
// Trigger reflow
|
||||
let window = window_from_node(self);
|
||||
|
@ -309,6 +309,9 @@ impl HTMLImageElement {
|
|||
/// Makes the local `image` member match the status of the `src` attribute and starts
|
||||
/// prefetching the image. This method must be called after `src` is changed.
|
||||
fn update_image(&self, value: Option<(DOMString, ServoUrl)>) {
|
||||
// Force any in-progress request to be ignored.
|
||||
self.generation.set(self.generation.get() + 1);
|
||||
|
||||
let document = document_from_node(self);
|
||||
let window = document.window();
|
||||
match value {
|
||||
|
@ -381,6 +384,7 @@ impl HTMLImageElement {
|
|||
metadata: None,
|
||||
blocker: None,
|
||||
}),
|
||||
generation: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,6 +626,15 @@ impl VirtualMethods for HTMLImageElement {
|
|||
Some(self.upcast::<HTMLElement>() as &VirtualMethods)
|
||||
}
|
||||
|
||||
fn adopting_steps(&self, old_doc: &Document) {
|
||||
self.super_type().unwrap().adopting_steps(old_doc);
|
||||
|
||||
let elem = self.upcast::<Element>();
|
||||
let document = document_from_node(self);
|
||||
self.update_image(Some((elem.get_string_attribute(&local_name!("src")),
|
||||
document.base_url())));
|
||||
}
|
||||
|
||||
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
||||
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
||||
match attr.local_name() {
|
||||
|
|
|
@ -483,6 +483,10 @@ pub struct ScriptThread {
|
|||
|
||||
/// A handle to the webvr thread, if available
|
||||
webvr_thread: Option<IpcSender<WebVRMsg>>,
|
||||
|
||||
/// A list of pipelines containing documents that finished loading all their blocking
|
||||
/// resources during a turn of the event loop.
|
||||
docs_with_no_blocking_loads: DOMRefCell<HashSet<JS<Document>>>,
|
||||
}
|
||||
|
||||
/// In the event of thread panic, all data on the stack runs its destructor. However, there
|
||||
|
@ -567,6 +571,15 @@ impl ScriptThreadFactory for ScriptThread {
|
|||
}
|
||||
|
||||
impl ScriptThread {
|
||||
pub fn mark_document_with_no_blocked_loads(doc: &Document) {
|
||||
SCRIPT_THREAD_ROOT.with(|root| {
|
||||
let script_thread = unsafe { &*root.get().unwrap() };
|
||||
script_thread.docs_with_no_blocking_loads
|
||||
.borrow_mut()
|
||||
.insert(JS::from_ref(doc));
|
||||
})
|
||||
}
|
||||
|
||||
pub fn invoke_perform_a_microtask_checkpoint() {
|
||||
SCRIPT_THREAD_ROOT.with(|root| {
|
||||
let script_thread = unsafe { &*root.get().unwrap() };
|
||||
|
@ -704,7 +717,9 @@ impl ScriptThread {
|
|||
|
||||
layout_to_constellation_chan: state.layout_to_constellation_chan,
|
||||
|
||||
webvr_thread: state.webvr_thread
|
||||
webvr_thread: state.webvr_thread,
|
||||
|
||||
docs_with_no_blocking_loads: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -885,6 +900,15 @@ impl ScriptThread {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/#the-end step 6
|
||||
let mut docs = self.docs_with_no_blocking_loads.borrow_mut();
|
||||
for document in docs.iter() {
|
||||
document.maybe_queue_document_completion();
|
||||
}
|
||||
docs.clear();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 7.12
|
||||
|
||||
// Issue batched reflows on any pages that require it (e.g. if images loaded)
|
||||
|
|
|
@ -54,7 +54,8 @@ fn is_unrooted_ty(cx: &LateContext, ty: &ty::TyS, in_new_function: bool) -> bool
|
|||
|| match_def_path(cx, did.did, &["core", "slice", "Iter"])
|
||||
|| match_def_path(cx, did.did, &["std", "collections", "hash", "map", "Entry"])
|
||||
|| match_def_path(cx, did.did, &["std", "collections", "hash", "map", "OccupiedEntry"])
|
||||
|| match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"]) {
|
||||
|| match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"])
|
||||
|| match_def_path(cx, did.did, &["std", "collections", "hash", "set", "Iter"]) {
|
||||
// Structures which are semantically similar to an &ptr.
|
||||
false
|
||||
} else if did.is_box() && in_new_function {
|
||||
|
|
|
@ -8337,6 +8337,18 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/document-adopt-base-url.html": [
|
||||
[
|
||||
"/html/semantics/embedded-content/the-img-element/document-adopt-base-url.html",
|
||||
[
|
||||
[
|
||||
"/html/semantics/embedded-content/the-img-element/document-base-url-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/document-base-url.html": [
|
||||
[
|
||||
"/html/semantics/embedded-content/the-img-element/document-base-url.html",
|
||||
|
@ -93287,6 +93299,12 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/delay-load-event.html": [
|
||||
[
|
||||
"/html/semantics/embedded-content/the-img-element/delay-load-event.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html": [
|
||||
[
|
||||
"/html/semantics/embedded-content/the-img-element/environment-changes/viewport-change.html",
|
||||
|
@ -176314,6 +176332,14 @@
|
|||
"bdbfbe9a5908c6233bd7b9697a0762bd2e0f6ede",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/delay-load-event.html": [
|
||||
"e4782535af755b29864fd3de67bbdd0de13f19d7",
|
||||
"testharness"
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/document-adopt-base-url.html": [
|
||||
"a4b542eb344cca6bdcceceb3aa7006e900f5400f",
|
||||
"reftest"
|
||||
],
|
||||
"html/semantics/embedded-content/the-img-element/document-base-url-ref.html": [
|
||||
"add78257076d22891334b93c8072d098ace9b6eb",
|
||||
"support"
|
||||
|
|
|
@ -12892,6 +12892,12 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"mozilla/img_multiple_request.html": [
|
||||
[
|
||||
"/_mozilla/mozilla/img_multiple_request.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"mozilla/img_width_height.html": [
|
||||
[
|
||||
"/_mozilla/mozilla/img_width_height.html",
|
||||
|
@ -25375,6 +25381,10 @@
|
|||
"3c4f36abed83367c851d943b1f25b8394de6fe75",
|
||||
"testharness"
|
||||
],
|
||||
"mozilla/img_multiple_request.html": [
|
||||
"0a6263ad87c9b3307f2dc694747b094a0517b79b",
|
||||
"testharness"
|
||||
],
|
||||
"mozilla/img_width_height.html": [
|
||||
"37a04735261a6d2b36c3d529ce81eda46ed6967e",
|
||||
"testharness"
|
||||
|
|
25
tests/wpt/mozilla/tests/mozilla/img_multiple_request.html
Normal file
25
tests/wpt/mozilla/tests/mozilla/img_multiple_request.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
async_test(function(t) {
|
||||
var i = new Image();
|
||||
i.src = "2x2.png";
|
||||
i.src = "2x2.png";
|
||||
i.onload = t.step_func(function() {
|
||||
i.onload = this.unreached_func("Load event for aborted request.");
|
||||
t.step_timeout(t.step_func_done(), 100);
|
||||
});
|
||||
}, "Multiple requests for the same URL do not trigger multiple load events.");
|
||||
|
||||
async_test(function(t) {
|
||||
var i = new Image();
|
||||
i.src = "test.png";
|
||||
i.src = "2x2.png";
|
||||
i.onload = t.step_func(function() {
|
||||
i.onload = this.unreached_func("Load event for aborted request.");
|
||||
t.step_timeout(t.step_func_done(), 100);
|
||||
});
|
||||
}, "Multiple requests for different URL do not trigger multiple load events.");
|
||||
</script>
|
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Image element delays window's load event</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<img src="resources/cat.jpg?pipe=trickle(d2)">
|
||||
<script>
|
||||
async_test(function(t) {
|
||||
var saw_img_load = false;
|
||||
document.querySelector('img').onload = t.step_func(function() {
|
||||
saw_img_load = true;
|
||||
});
|
||||
addEventListener('load', t.step_func_done(function() {
|
||||
assert_true(saw_img_load);
|
||||
}));
|
||||
});
|
||||
</script>
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue