From 8249be38c9561f80b611610431a3e97531f67683 Mon Sep 17 00:00:00 2001 From: Bastien Orivel Date: Thu, 7 May 2020 21:27:56 +0200 Subject: [PATCH 1/2] Implement cross origin resource policy check I removed the window getter usage from those tests as servo does not support that yet. --- components/layout_2020/context.rs | 2 +- components/layout_2020/replaced.rs | 2 +- components/net/http_loader.rs | 92 ++++++++++++++++++- components/net_traits/response.rs | 2 +- tests/wpt/metadata/MANIFEST.json | 4 +- .../fetch-in-iframe.html.ini | 12 --- .../fetch.any.js.ini | 42 --------- .../fetch.https.any.js.ini | 24 ----- .../iframe-loads.html.ini | 2 - .../image-loads.html.ini | 19 ---- .../scheme-restriction.any.js.ini | 9 -- .../script-loads.html.ini | 19 ---- .../image-loads.html | 1 + .../script-loads.html | 1 + 14 files changed, 98 insertions(+), 133 deletions(-) delete mode 100644 tests/wpt/metadata/fetch/cross-origin-resource-policy/image-loads.html.ini delete mode 100644 tests/wpt/metadata/fetch/cross-origin-resource-policy/scheme-restriction.any.js.ini delete mode 100644 tests/wpt/metadata/fetch/cross-origin-resource-policy/script-loads.html.ini diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs index 109a1a6935b..1c9a0f7162e 100644 --- a/components/layout_2020/context.rs +++ b/components/layout_2020/context.rs @@ -113,7 +113,7 @@ impl<'a> LayoutContext<'a> { } match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { - Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => { + Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { let image_info = WebRenderImageInfo { width: image.width, height: image.height, diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs index f3501ce0e71..d10e03198ac 100644 --- a/components/layout_2020/replaced.rs +++ b/components/layout_2020/replaced.rs @@ -125,7 +125,7 @@ impl ReplacedContent { image_url.clone(), UsePlaceholder::No, ) { - Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => { + Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { (Some(image.clone()), image.width as f32, image.height as f32) }, Some(ImageOrMetadataAvailable::MetadataAvailable(metadata)) => { diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index c7002c4c9b4..db14a9ca4d3 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -33,6 +33,7 @@ use http::{HeaderMap, Request as HyperRequest}; use hyper::{Body, Client, Method, Response as HyperResponse, StatusCode}; use hyper_serde::Serde; use msg::constellation_msg::{HistoryStateId, PipelineId}; +use net_traits::pub_domains::reg_suffix; use net_traits::quality::{quality_to_value, Quality, QualityItem}; use net_traits::request::Origin::Origin as SpecificOrigin; use net_traits::request::{is_cors_safelisted_method, is_cors_safelisted_request_header}; @@ -189,6 +190,28 @@ fn strict_origin_when_cross_origin(referrer_url: ServoUrl, url: ServoUrl) -> Opt strip_url(referrer_url, cross_origin) } +/// https://html.spec.whatwg.org/multipage/#schemelessly-same-site +fn is_schemelessy_same_site(site_a: &ImmutableOrigin, site_b: &ImmutableOrigin) -> bool { + // Step 1 + if !site_a.is_tuple() && !site_b.is_tuple() && site_a == site_b { + true + } else if site_a.is_tuple() && site_b.is_tuple() { + // Step 2.1 + let host_a = site_a.host().map(|h| h.to_string()).unwrap_or_default(); + let host_b = site_b.host().map(|h| h.to_string()).unwrap_or_default(); + + let host_a_reg = reg_suffix(&host_a); + let host_b_reg = reg_suffix(&host_b); + + // Step 2.2-2.3 + (site_a.host() == site_b.host() && host_a_reg == "") || + (host_a_reg == host_b_reg && host_a_reg != "") + } else { + // Step 3 + false + } +} + /// fn strip_url(mut referrer_url: ServoUrl, origin_only: bool) -> Option { const MAX_REFERRER_URL_LENGTH: usize = 4096; @@ -1251,7 +1274,74 @@ fn http_network_or_cache_fetch( // TODO: if necessary set response's range-requested flag // Step 9 - // TODO: handle CORS not set and cross-origin blocked + // https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check + #[derive(PartialEq)] + enum CrossOriginResourcePolicy { + Allowed, + Blocked, + } + + fn cross_origin_resource_policy_check( + request: &Request, + response: &Response, + ) -> CrossOriginResourcePolicy { + // Step 1 + if request.mode != RequestMode::NoCors { + return CrossOriginResourcePolicy::Allowed; + } + + // Step 2 + let current_url_origin = request.current_url().origin(); + let same_origin = if let Origin::Origin(ref origin) = request.origin { + *origin == request.current_url().origin() + } else { + false + }; + + if same_origin { + return CrossOriginResourcePolicy::Allowed; + } + + // Step 3 + let policy = response + .headers + .get(HeaderName::from_static("cross-origin-resource-policy")) + .map(|h| h.to_str().unwrap_or("")) + .unwrap_or(""); + + // Step 4 + if policy == "same-origin" { + return CrossOriginResourcePolicy::Blocked; + } + + // Step 5 + if let Origin::Origin(ref request_origin) = request.origin { + let schemeless_same_origin = + is_schemelessy_same_site(&request_origin, ¤t_url_origin); + if schemeless_same_origin && + (request_origin.scheme() == Some("https") || + response.https_state == HttpsState::None) + { + return CrossOriginResourcePolicy::Allowed; + } + }; + + // Step 6 + if policy == "same-site" { + return CrossOriginResourcePolicy::Blocked; + } + + CrossOriginResourcePolicy::Allowed + } + + if http_request.response_tainting != ResponseTainting::CorsTainting && + cross_origin_resource_policy_check(&http_request, &response) == + CrossOriginResourcePolicy::Blocked + { + return Response::network_error(NetworkError::Internal( + "Cross-origin resource policy check failed".into(), + )); + } // Step 10 // FIXME: Figure out what to do with request window objects diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs index b2342a332b4..0c12546bebe 100644 --- a/components/net_traits/response.rs +++ b/components/net_traits/response.rs @@ -61,7 +61,7 @@ pub enum CacheState { } /// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state) -#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum HttpsState { None, Deprecated, diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index d35ece1af97..98f26212f58 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -424333,7 +424333,7 @@ ] ], "image-loads.html": [ - "8a0458f107abdf2b7d6664fb8194e6b4b0222989", + "060b7551ea516837cf416c797e85474658857632", [ null, {} @@ -424379,7 +424379,7 @@ ] ], "script-loads.html": [ - "5850e0109f18c23e40d73686bef5e4b6a6b40686", + "a9690fc70be13885d7ca6448730c83f755810774", [ null, {} diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch-in-iframe.html.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch-in-iframe.html.ini index 3951cd266d5..293bf2f9f27 100644 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch-in-iframe.html.ini +++ b/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch-in-iframe.html.ini @@ -5,15 +5,3 @@ [fetch-in-iframe] expected: FAIL - [Cross-origin fetch in a data: iframe load fails if the server blocks cross-origin loads with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin fetch in a data: iframe load fails if the server blocks cross-origin loads with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin fetch in a cross origin iframe load fails if the server blocks cross-origin loads with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin fetch in a cross origin iframe load fails if the server blocks cross-origin loads with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.any.js.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.any.js.ini index 55e4b8ec274..20f8d0fffc6 100644 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.any.js.ini +++ b/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.any.js.ini @@ -7,53 +7,11 @@ [fetch] expected: FAIL - [Valid cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header after a redirection.] - expected: FAIL - - [Cross-origin no-cors fetch to a same-site URL with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' redirect response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-scheme (HTTP to HTTPS) no-cors fetch to a same-site URL with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - [fetch.any.worker.html] [fetch] expected: FAIL - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin no-cors fetch to a same-site URL with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Valid cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header after a redirection.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' redirect response header.] - expected: FAIL - - [Cross-scheme (HTTP to HTTPS) no-cors fetch to a same-site URL with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - [fetch.any.sharedworker.html] expected: ERROR diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.https.any.js.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.https.any.js.ini index 2c43b33a3a1..db4cfcfdf94 100644 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.https.any.js.ini +++ b/tests/wpt/metadata/fetch/cross-origin-resource-policy/fetch.https.any.js.ini @@ -1,16 +1,4 @@ [fetch.https.any.html] - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header after a redirection.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' redirect response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - [fetch.https.any.serviceworker.html] expected: ERROR @@ -25,18 +13,6 @@ [fetch.https.any.worker.html] - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header after a redirection.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' redirect response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors fetch with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - [fetch] expected: FAIL diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/iframe-loads.html.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/iframe-loads.html.ini index 62283b995da..df9e6ef13e0 100644 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/iframe-loads.html.ini +++ b/tests/wpt/metadata/fetch/cross-origin-resource-policy/iframe-loads.html.ini @@ -1,8 +1,6 @@ [iframe-loads.html] [Untitled] expected: FAIL - [Load an iframe that has Cross-Origin-Resource-Policy header] - expected: FAIL [iframe-loads] expected: FAIL diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/image-loads.html.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/image-loads.html.ini deleted file mode 100644 index e28824f5560..00000000000 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/image-loads.html.ini +++ /dev/null @@ -1,19 +0,0 @@ -[image-loads.html] - [Same-origin image load with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Same-origin image load with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin cors image load with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin cors image load with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin no-cors image load with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors image load with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/scheme-restriction.any.js.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/scheme-restriction.any.js.ini deleted file mode 100644 index 92ed8a87544..00000000000 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/scheme-restriction.any.js.ini +++ /dev/null @@ -1,9 +0,0 @@ -[scheme-restriction.any.html] - [Cross-Origin-Resource-Policy: same-site blocks retrieving HTTPS from HTTP] - expected: FAIL - - -[scheme-restriction.any.worker.html] - [Cross-Origin-Resource-Policy: same-site blocks retrieving HTTPS from HTTP] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/cross-origin-resource-policy/script-loads.html.ini b/tests/wpt/metadata/fetch/cross-origin-resource-policy/script-loads.html.ini deleted file mode 100644 index 1a9132484c8..00000000000 --- a/tests/wpt/metadata/fetch/cross-origin-resource-policy/script-loads.html.ini +++ /dev/null @@ -1,19 +0,0 @@ -[script-loads.html] - [Same-origin script load with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Same-origin script load with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin cors script load with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin cors script load with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - - [Cross-origin no-cors script load with a 'Cross-Origin-Resource-Policy: same-origin' response header.] - expected: FAIL - - [Cross-origin no-cors script load with a 'Cross-Origin-Resource-Policy: same-site' response header.] - expected: FAIL - diff --git a/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/image-loads.html b/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/image-loads.html index 8a0458f107a..060b7551ea5 100644 --- a/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/image-loads.html +++ b/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/image-loads.html @@ -16,6 +16,7 @@ const noCors = false; function loadImage(url, shoudLoad, corsMode, title) { + const testDiv = document.getElementById("testDiv"); promise_test(() => { const img = new Image(); if (corsMode) diff --git a/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/script-loads.html b/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/script-loads.html index 5850e0109f1..a9690fc70be 100644 --- a/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/script-loads.html +++ b/tests/wpt/web-platform-tests/fetch/cross-origin-resource-policy/script-loads.html @@ -16,6 +16,7 @@ const noCors = false; function loadScript(url, shoudLoad, corsMode, title) { + const testDiv = document.getElementById("testDiv"); promise_test(() => { const script = document.createElement("script"); if (corsMode) From bdbfde9ec0b5e80af0f63777b4cf2b31f5209f92 Mon Sep 17 00:00:00 2001 From: Bastien Orivel Date: Fri, 8 May 2020 15:36:55 +0200 Subject: [PATCH 2/2] Don't send a load event when a loaded image is actually the placeholder The image cache returns an `ImageCacheResult::ImageAvailable `the second time you try getting the placeholder. This means that in some cases, the loading of an image would fail, then the same image would get fetched from the cache, the placeholder would be loaded from that but would be seen as a normal image, firing a load event. This made the tests in `fetch/cross-origin-resource-policy/image-loads.html` fail depending on their order. --- components/layout/context.rs | 2 +- components/layout/fragment.rs | 4 ++-- components/net/image_cache.rs | 20 ++++++++++++++------ components/net_traits/image_cache.rs | 7 ++++++- components/script/dom/htmlimageelement.rs | 14 +++++++++++--- components/script/dom/htmlvideoelement.rs | 8 ++++++-- 6 files changed, 40 insertions(+), 15 deletions(-) diff --git a/components/layout/context.rs b/components/layout/context.rs index 536d108ea04..00e0c5a0cc1 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -163,7 +163,7 @@ impl<'a> LayoutContext<'a> { } match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { - Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => { + Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { let image_info = WebRenderImageInfo::from_image(&*image); if image_info.key.is_none() { Some(image_info) diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 289dc3f3094..062db9c0a7e 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -431,8 +431,8 @@ impl ImageFragmentInfo { layout_context .get_or_request_image_or_meta(node.opaque(), url, UsePlaceholder::Yes) .map(|result| match result { - ImageOrMetadataAvailable::ImageAvailable(i, _) => { - ImageOrMetadata::Image(i) + ImageOrMetadataAvailable::ImageAvailable { image, .. } => { + ImageOrMetadata::Image(image) }, ImageOrMetadataAvailable::MetadataAvailable(m) => { ImageOrMetadata::Metadata(m) diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index d32061da06a..8a5e6a53749 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -466,9 +466,12 @@ impl ImageCache for ImageCacheImpl { match result { Ok((image, image_url)) => { debug!("{} is available", url); - return ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable( - image, image_url, - )); + let is_placeholder = image_url == store.placeholder_url; + return ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { + image, + url: image_url, + is_placeholder, + }); }, Err(()) => { debug!("{} is not available", url); @@ -515,9 +518,14 @@ impl ImageCache for ImageCacheImpl { // TODO: make this behaviour configurable according to the caller's needs. store.handle_decoder(decoded); match store.get_completed_image_if_available(url, origin, cors_setting, use_placeholder) { - Some(Ok((image, image_url))) => ImageCacheResult::Available( - ImageOrMetadataAvailable::ImageAvailable(image, image_url), - ), + Some(Ok((image, image_url))) => { + let is_placeholder = image_url == store.placeholder_url; + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { + image, + url: image_url, + is_placeholder, + }) + }, _ => ImageCacheResult::LoadError, } } diff --git a/components/net_traits/image_cache.rs b/components/net_traits/image_cache.rs index 39d501a271f..e3c1a565881 100644 --- a/components/net_traits/image_cache.rs +++ b/components/net_traits/image_cache.rs @@ -17,7 +17,12 @@ use std::sync::Arc; /// Indicating either entire image or just metadata availability #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum ImageOrMetadataAvailable { - ImageAvailable(#[ignore_malloc_size_of = "Arc"] Arc, ServoUrl), + ImageAvailable { + #[ignore_malloc_size_of = "Arc"] + image: Arc, + url: ServoUrl, + is_placeholder: bool, + }, MetadataAvailable(ImageMetadata), } diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 8041fb5685e..2e99e491653 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -329,8 +329,16 @@ impl HTMLImageElement { ); match cache_result { - ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable(image, url)) => { - self.process_image_response(ImageResponse::Loaded(image, url)) + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { + image, + url, + is_placeholder, + }) => { + if is_placeholder { + self.process_image_response(ImageResponse::PlaceholderLoaded(image, url)) + } else { + self.process_image_response(ImageResponse::Loaded(image, url)) + } }, ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(m)) => { self.process_image_response(ImageResponse::MetadataLoaded(m)) @@ -1109,7 +1117,7 @@ impl HTMLImageElement { ); match cache_result { - ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable(_image, _url)) => { + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { .. }) => { // Step 15 self.finish_reacting_to_environment_change( selected_source, diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index c06dc083a22..cea37bc173c 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -166,8 +166,12 @@ impl HTMLVideoElement { ); match cache_result { - ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable(img, url)) => { - self.process_image_response(ImageResponse::Loaded(img, url)); + ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable { + image, + url, + .. + }) => { + self.process_image_response(ImageResponse::Loaded(image, url)); }, ImageCacheResult::ReadyForRequest(id) => { self.do_fetch_poster_frame(poster_url, id, cancel_receiver)