mirror of
https://github.com/servo/servo.git
synced 2025-06-08 16:43:28 +00:00
Instead of creating a `ROUTER` for each fetch, create a fetch thread which handles all incoming and outcoming fetch requests. Now messages involving fetches carry a "request id" which indicates which fetch is being addressed by the message. This greatly reduces the number of file descriptors used by fetch. In addition, the interface for kicking off fetches is simplified when using the `Listener` with `Document`s and the `GlobalScope`. This does not fix all leaked file descriptors / mach ports, but greatly eliminates the number used. Now tests can be run without limiting procesess on modern macOS systems. Followup work: 1. There are more instances where fetch is done using the old method. Some of these require more changes in order to be converted to the `FetchThread` approach. 2. Eliminate usage of IPC channels when doing redirects. 3. Also eliminate the IPC channel used for cancel handling. 4. This change opens up the possiblity of controlling the priority of fetch requests. Fixes #29834. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
408 lines
14 KiB
Rust
408 lines
14 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::cell::Cell;
|
|
use std::sync::Arc;
|
|
|
|
use dom_struct::dom_struct;
|
|
use euclid::default::Size2D;
|
|
use html5ever::{local_name, LocalName, Prefix};
|
|
use ipc_channel::ipc;
|
|
use js::rust::HandleObject;
|
|
use net_traits::image_cache::{
|
|
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, PendingImageId,
|
|
UsePlaceholder,
|
|
};
|
|
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
|
|
use net_traits::{
|
|
FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
|
|
ResourceTimingType,
|
|
};
|
|
use servo_media::player::video::VideoFrame;
|
|
use servo_url::ServoUrl;
|
|
|
|
use crate::document_loader::{LoadBlocker, LoadType};
|
|
use crate::dom::attr::Attr;
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::HTMLVideoElementBinding::HTMLVideoElementMethods;
|
|
use crate::dom::bindings::inheritance::Castable;
|
|
use crate::dom::bindings::refcounted::Trusted;
|
|
use crate::dom::bindings::reflector::DomObject;
|
|
use crate::dom::bindings::root::DomRoot;
|
|
use crate::dom::bindings::str::DOMString;
|
|
use crate::dom::document::Document;
|
|
use crate::dom::element::{AttributeMutation, Element};
|
|
use crate::dom::globalscope::GlobalScope;
|
|
use crate::dom::htmlmediaelement::{HTMLMediaElement, ReadyState};
|
|
use crate::dom::node::{document_from_node, window_from_node, Node};
|
|
use crate::dom::performanceresourcetiming::InitiatorType;
|
|
use crate::dom::virtualmethods::VirtualMethods;
|
|
use crate::fetch::FetchCanceller;
|
|
use crate::image_listener::{generate_cache_listener_for_element, ImageCacheListener};
|
|
use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
|
|
use crate::script_runtime::CanGc;
|
|
|
|
const DEFAULT_WIDTH: u32 = 300;
|
|
const DEFAULT_HEIGHT: u32 = 150;
|
|
|
|
#[dom_struct]
|
|
pub struct HTMLVideoElement {
|
|
htmlmediaelement: HTMLMediaElement,
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-video-videowidth>
|
|
video_width: Cell<u32>,
|
|
/// <https://html.spec.whatwg.org/multipage/#dom-video-videoheight>
|
|
video_height: Cell<u32>,
|
|
/// Incremented whenever tasks associated with this element are cancelled.
|
|
generation_id: Cell<u32>,
|
|
/// Poster frame fetch request canceller.
|
|
poster_frame_canceller: DomRefCell<FetchCanceller>,
|
|
/// Load event blocker. Will block the load event while the poster frame
|
|
/// is being fetched.
|
|
load_blocker: DomRefCell<Option<LoadBlocker>>,
|
|
/// A copy of the last frame
|
|
#[ignore_malloc_size_of = "VideoFrame"]
|
|
#[no_trace]
|
|
last_frame: DomRefCell<Option<VideoFrame>>,
|
|
}
|
|
|
|
impl HTMLVideoElement {
|
|
fn new_inherited(
|
|
local_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
) -> HTMLVideoElement {
|
|
HTMLVideoElement {
|
|
htmlmediaelement: HTMLMediaElement::new_inherited(local_name, prefix, document),
|
|
video_width: Cell::new(DEFAULT_WIDTH),
|
|
video_height: Cell::new(DEFAULT_HEIGHT),
|
|
generation_id: Cell::new(0),
|
|
poster_frame_canceller: DomRefCell::new(Default::default()),
|
|
load_blocker: Default::default(),
|
|
last_frame: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[allow(crown::unrooted_must_root)]
|
|
pub fn new(
|
|
local_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
proto: Option<HandleObject>,
|
|
) -> DomRoot<HTMLVideoElement> {
|
|
Node::reflect_node_with_proto(
|
|
Box::new(HTMLVideoElement::new_inherited(
|
|
local_name, prefix, document,
|
|
)),
|
|
document,
|
|
proto,
|
|
)
|
|
}
|
|
|
|
pub fn get_video_width(&self) -> u32 {
|
|
self.video_width.get()
|
|
}
|
|
|
|
pub fn set_video_width(&self, width: u32) {
|
|
self.video_width.set(width);
|
|
}
|
|
|
|
pub fn get_video_height(&self) -> u32 {
|
|
self.video_height.get()
|
|
}
|
|
|
|
pub fn set_video_height(&self, height: u32) {
|
|
self.video_height.set(height);
|
|
}
|
|
|
|
pub fn get_current_frame_data(&self) -> Option<(Option<ipc::IpcSharedMemory>, Size2D<u32>)> {
|
|
let frame = self.htmlmediaelement.get_current_frame();
|
|
if frame.is_some() {
|
|
*self.last_frame.borrow_mut() = frame;
|
|
}
|
|
|
|
match self.last_frame.borrow().as_ref() {
|
|
Some(frame) => {
|
|
let size = Size2D::new(frame.get_width() as u32, frame.get_height() as u32);
|
|
if !frame.is_gl_texture() {
|
|
let data = Some(ipc::IpcSharedMemory::from_bytes(&frame.get_data()));
|
|
Some((data, size))
|
|
} else {
|
|
// XXX(victor): here we only have the GL texture ID.
|
|
Some((None, size))
|
|
}
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
|
|
fn fetch_poster_frame(&self, poster_url: &str, can_gc: CanGc) {
|
|
// Step 1.
|
|
let cancel_receiver = self.poster_frame_canceller.borrow_mut().initialize();
|
|
self.generation_id.set(self.generation_id.get() + 1);
|
|
|
|
// Step 2.
|
|
if poster_url.is_empty() {
|
|
return;
|
|
}
|
|
|
|
// Step 3.
|
|
let poster_url = match document_from_node(self).url().join(poster_url) {
|
|
Ok(url) => url,
|
|
Err(_) => return,
|
|
};
|
|
|
|
// Step 4.
|
|
// We use the image cache for poster frames so we save as much
|
|
// network activity as possible.
|
|
let window = window_from_node(self);
|
|
let image_cache = window.image_cache();
|
|
let sender = generate_cache_listener_for_element(self);
|
|
let cache_result = image_cache.track_image(
|
|
poster_url.clone(),
|
|
window.origin().immutable().clone(),
|
|
None,
|
|
sender,
|
|
UsePlaceholder::No,
|
|
);
|
|
|
|
match cache_result {
|
|
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
|
|
image,
|
|
url,
|
|
..
|
|
}) => {
|
|
self.process_image_response(ImageResponse::Loaded(image, url), can_gc);
|
|
},
|
|
ImageCacheResult::ReadyForRequest(id) => {
|
|
self.do_fetch_poster_frame(poster_url, id, cancel_receiver, can_gc)
|
|
},
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
|
|
fn do_fetch_poster_frame(
|
|
&self,
|
|
poster_url: ServoUrl,
|
|
id: PendingImageId,
|
|
cancel_receiver: ipc::IpcReceiver<()>,
|
|
can_gc: CanGc,
|
|
) {
|
|
// Continuation of step 4.
|
|
let document = document_from_node(self);
|
|
let request = RequestBuilder::new(poster_url.clone(), document.global().get_referrer())
|
|
.destination(Destination::Image)
|
|
.credentials_mode(CredentialsMode::Include)
|
|
.use_url_credentials(true)
|
|
.origin(document.origin().immutable().clone())
|
|
.pipeline_id(Some(document.global().pipeline_id()));
|
|
|
|
// Step 5.
|
|
// This delay must be independent from the ones created by HTMLMediaElement during
|
|
// its media load algorithm, otherwise a code like
|
|
// <video poster="poster.png"></video>
|
|
// (which triggers no media load algorithm unless a explicit call to .load() is done)
|
|
// will block the document's load event forever.
|
|
let mut blocker = self.load_blocker.borrow_mut();
|
|
LoadBlocker::terminate(&mut blocker, can_gc);
|
|
*blocker = Some(LoadBlocker::new(
|
|
&document_from_node(self),
|
|
LoadType::Image(poster_url.clone()),
|
|
));
|
|
|
|
let context = PosterFrameFetchContext::new(self, poster_url, id);
|
|
|
|
// TODO: If this is supposed to to be a "fetch" as defined in the specification
|
|
// this should probably be integrated into the Document's list of cancellable fetches.
|
|
document_from_node(self).fetch_background(request, context, Some(cancel_receiver));
|
|
}
|
|
}
|
|
|
|
impl HTMLVideoElementMethods for HTMLVideoElement {
|
|
// https://html.spec.whatwg.org/multipage/#dom-video-videowidth
|
|
fn VideoWidth(&self) -> u32 {
|
|
if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
|
|
return 0;
|
|
}
|
|
self.video_width.get()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-video-videoheight
|
|
fn VideoHeight(&self) -> u32 {
|
|
if self.htmlmediaelement.get_ready_state() == ReadyState::HaveNothing {
|
|
return 0;
|
|
}
|
|
self.video_height.get()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-video-poster
|
|
make_getter!(Poster, "poster");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-video-poster
|
|
make_setter!(SetPoster, "poster");
|
|
|
|
// For testing purposes only. This is not an event from
|
|
// https://html.spec.whatwg.org/multipage/#dom-video-poster
|
|
event_handler!(postershown, GetOnpostershown, SetOnpostershown);
|
|
}
|
|
|
|
impl VirtualMethods for HTMLVideoElement {
|
|
fn super_type(&self) -> Option<&dyn VirtualMethods> {
|
|
Some(self.upcast::<HTMLMediaElement>() as &dyn VirtualMethods)
|
|
}
|
|
|
|
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
|
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
|
|
|
if let Some(new_value) = mutation.new_value(attr) {
|
|
if attr.local_name() == &local_name!("poster") {
|
|
self.fetch_poster_frame(&new_value, CanGc::note());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ImageCacheListener for HTMLVideoElement {
|
|
fn generation_id(&self) -> u32 {
|
|
self.generation_id.get()
|
|
}
|
|
|
|
fn process_image_response(&self, response: ImageResponse, can_gc: CanGc) {
|
|
match response {
|
|
ImageResponse::Loaded(image, url) => {
|
|
debug!("Loaded poster image for video element: {:?}", url);
|
|
self.htmlmediaelement.process_poster_image_loaded(image);
|
|
LoadBlocker::terminate(&mut self.load_blocker.borrow_mut(), can_gc);
|
|
},
|
|
ImageResponse::MetadataLoaded(..) => {},
|
|
// The image cache may have loaded a placeholder for an invalid poster url
|
|
ImageResponse::PlaceholderLoaded(..) | ImageResponse::None => {
|
|
// A failed load should unblock the document load.
|
|
LoadBlocker::terminate(&mut self.load_blocker.borrow_mut(), can_gc);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PosterFrameFetchContext {
|
|
/// Reference to the script thread image cache.
|
|
image_cache: Arc<dyn ImageCache>,
|
|
/// The element that initiated the request.
|
|
elem: Trusted<HTMLVideoElement>,
|
|
/// The cache ID for this request.
|
|
id: PendingImageId,
|
|
/// True if this response is invalid and should be ignored.
|
|
cancelled: bool,
|
|
/// Timing data for this resource
|
|
resource_timing: ResourceFetchTiming,
|
|
/// Url for the resource
|
|
url: ServoUrl,
|
|
}
|
|
|
|
impl FetchResponseListener for PosterFrameFetchContext {
|
|
fn process_request_body(&mut self, _: RequestId) {}
|
|
fn process_request_eof(&mut self, _: RequestId) {}
|
|
|
|
fn process_response(
|
|
&mut self,
|
|
request_id: RequestId,
|
|
metadata: Result<FetchMetadata, NetworkError>,
|
|
) {
|
|
self.image_cache.notify_pending_response(
|
|
self.id,
|
|
FetchResponseMsg::ProcessResponse(request_id, metadata.clone()),
|
|
);
|
|
|
|
let metadata = metadata.ok().map(|meta| match meta {
|
|
FetchMetadata::Unfiltered(m) => m,
|
|
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
|
|
});
|
|
|
|
let status_is_ok = metadata
|
|
.as_ref()
|
|
.map_or(true, |m| m.status.in_range(200..300));
|
|
|
|
if !status_is_ok {
|
|
self.cancelled = true;
|
|
self.elem
|
|
.root()
|
|
.poster_frame_canceller
|
|
.borrow_mut()
|
|
.cancel();
|
|
}
|
|
}
|
|
|
|
fn process_response_chunk(&mut self, request_id: RequestId, payload: Vec<u8>) {
|
|
if self.cancelled {
|
|
// An error was received previously, skip processing the payload.
|
|
return;
|
|
}
|
|
|
|
self.image_cache.notify_pending_response(
|
|
self.id,
|
|
FetchResponseMsg::ProcessResponseChunk(request_id, payload),
|
|
);
|
|
}
|
|
|
|
fn process_response_eof(
|
|
&mut self,
|
|
request_id: RequestId,
|
|
response: Result<ResourceFetchTiming, NetworkError>,
|
|
) {
|
|
self.image_cache.notify_pending_response(
|
|
self.id,
|
|
FetchResponseMsg::ProcessResponseEOF(request_id, response),
|
|
);
|
|
}
|
|
|
|
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
|
|
&mut self.resource_timing
|
|
}
|
|
|
|
fn resource_timing(&self) -> &ResourceFetchTiming {
|
|
&self.resource_timing
|
|
}
|
|
|
|
fn submit_resource_timing(&mut self) {
|
|
network_listener::submit_timing(self)
|
|
}
|
|
}
|
|
|
|
impl ResourceTimingListener for PosterFrameFetchContext {
|
|
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
|
|
let initiator_type = InitiatorType::LocalName(
|
|
self.elem
|
|
.root()
|
|
.upcast::<Element>()
|
|
.local_name()
|
|
.to_string(),
|
|
);
|
|
(initiator_type, self.url.clone())
|
|
}
|
|
|
|
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
|
|
document_from_node(&*self.elem.root()).global()
|
|
}
|
|
}
|
|
|
|
impl PreInvoke for PosterFrameFetchContext {
|
|
fn should_invoke(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
impl PosterFrameFetchContext {
|
|
fn new(elem: &HTMLVideoElement, url: ServoUrl, id: PendingImageId) -> PosterFrameFetchContext {
|
|
let window = window_from_node(elem);
|
|
PosterFrameFetchContext {
|
|
image_cache: window.image_cache(),
|
|
elem: Trusted::new(elem),
|
|
id,
|
|
cancelled: false,
|
|
resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
|
|
url,
|
|
}
|
|
}
|
|
}
|