mirror of
https://github.com/servo/servo
synced 2026-05-09 16:42:16 +02:00
This implements the required plumbing for the keep-alive flag on requests. The flag already exists on the request object, but now also exists on the builder itself. The flag is then passed into a canceller. While we could make canceller optional, in many cases it isn't and would require a whole lot more changes. To follow the spec more closely, opted to put the keep_alive flag on the canceller as well. Note that this doesn't pass any extra fetch-later tests since we are not correctly unloading iframe documents. That will be fixed in a follow-up PR. Part of #41230 Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
209 lines
6.6 KiB
Rust
209 lines
6.6 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/. */
|
||
|
||
//! Tracking of pending loads in a document.
|
||
//!
|
||
//! <https://html.spec.whatwg.org/multipage/#the-end>
|
||
|
||
use net_traits::request::RequestBuilder;
|
||
use net_traits::{BoxedFetchCallback, ResourceThreads, fetch_async};
|
||
use servo_url::ServoUrl;
|
||
|
||
use crate::dom::bindings::cell::DomRefCell;
|
||
use crate::dom::bindings::root::Dom;
|
||
use crate::dom::document::Document;
|
||
use crate::fetch::{FetchCanceller, QueuedDeferredFetchRecord};
|
||
use crate::script_runtime::CanGc;
|
||
|
||
#[derive(Clone, Debug, JSTraceable, MallocSizeOf, PartialEq)]
|
||
pub(crate) enum LoadType {
|
||
Image(#[no_trace] ServoUrl),
|
||
Script(#[no_trace] ServoUrl),
|
||
Subframe(#[no_trace] ServoUrl),
|
||
Stylesheet(#[no_trace] ServoUrl),
|
||
PageSource(#[no_trace] ServoUrl),
|
||
Media,
|
||
}
|
||
|
||
/// Canary value ensuring that manually added blocking loads (ie. ones that weren't
|
||
/// created via DocumentLoader::fetch_async) are always removed by the time
|
||
/// that the owner is destroyed.
|
||
#[derive(JSTraceable, MallocSizeOf)]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||
pub(crate) struct LoadBlocker {
|
||
/// The document whose load event is blocked by this object existing.
|
||
doc: Dom<Document>,
|
||
/// The load that is blocking the document's load event.
|
||
load: Option<LoadType>,
|
||
}
|
||
|
||
impl LoadBlocker {
|
||
/// Mark the document's load event as blocked on this new load.
|
||
pub(crate) fn new(doc: &Document, load: LoadType) -> LoadBlocker {
|
||
doc.loader_mut().add_blocking_load(load.clone());
|
||
LoadBlocker {
|
||
doc: Dom::from_ref(doc),
|
||
load: Some(load),
|
||
}
|
||
}
|
||
|
||
/// Remove this load from the associated document's list of blocking loads.
|
||
pub(crate) fn terminate(blocker: &DomRefCell<Option<LoadBlocker>>, can_gc: CanGc) {
|
||
let Some(load) = blocker
|
||
.borrow_mut()
|
||
.as_mut()
|
||
.and_then(|blocker| blocker.load.take())
|
||
else {
|
||
return;
|
||
};
|
||
|
||
if let Some(blocker) = blocker.borrow().as_ref() {
|
||
blocker.doc.finish_load(load, can_gc);
|
||
}
|
||
|
||
*blocker.borrow_mut() = None;
|
||
}
|
||
}
|
||
|
||
impl Drop for LoadBlocker {
|
||
fn drop(&mut self) {
|
||
if let Some(load) = self.load.take() {
|
||
self.doc.finish_load(load, CanGc::note());
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(JSTraceable, MallocSizeOf)]
|
||
pub(crate) struct DocumentLoader {
|
||
#[no_trace]
|
||
resource_threads: ResourceThreads,
|
||
blocking_loads: Vec<LoadType>,
|
||
events_inhibited: bool,
|
||
cancellers: Vec<FetchCanceller>,
|
||
/// <https://fetch.spec.whatwg.org/#fetch-group-deferred-fetch-records>
|
||
#[no_trace]
|
||
#[conditional_malloc_size_of]
|
||
deferred_fetches: Vec<QueuedDeferredFetchRecord>,
|
||
}
|
||
|
||
impl DocumentLoader {
|
||
pub(crate) fn new(existing: &DocumentLoader) -> DocumentLoader {
|
||
DocumentLoader::new_with_threads(existing.resource_threads.clone(), None)
|
||
}
|
||
|
||
pub(crate) fn new_with_threads(
|
||
resource_threads: ResourceThreads,
|
||
initial_load: Option<ServoUrl>,
|
||
) -> DocumentLoader {
|
||
debug!("Initial blocking load {:?}.", initial_load);
|
||
let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();
|
||
|
||
DocumentLoader {
|
||
resource_threads,
|
||
blocking_loads: initial_loads,
|
||
events_inhibited: false,
|
||
cancellers: Vec::new(),
|
||
deferred_fetches: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// <https://fetch.spec.whatwg.org/#concept-fetch-group-terminate>
|
||
pub(crate) fn cancel_all_loads(&mut self) -> bool {
|
||
// Step 1. For each fetch record record of fetchGroup’s fetch records,
|
||
// if record’s controller is non-null and record’s request’s done flag
|
||
// is unset and keepalive is false, terminate record’s controller.
|
||
//
|
||
// Implemented in [`FetchCanceller::cancel`] which runs when these cancellers are dropped.
|
||
let canceled_any = !self.cancellers.is_empty();
|
||
self.cancellers.clear();
|
||
// Step 2. Process deferred fetches for fetchGroup.
|
||
for deferred_fetch in self.deferred_fetches.drain(..) {
|
||
deferred_fetch.lock().unwrap().process();
|
||
}
|
||
|
||
canceled_any
|
||
}
|
||
|
||
/// Add a load to the list of blocking loads.
|
||
fn add_blocking_load(&mut self, load: LoadType) {
|
||
debug!(
|
||
"Adding blocking load {:?} ({}).",
|
||
load,
|
||
self.blocking_loads.len()
|
||
);
|
||
self.blocking_loads.push(load);
|
||
}
|
||
|
||
pub(crate) fn append_deferred_fetch(&mut self, deferred_fetch: QueuedDeferredFetchRecord) {
|
||
self.deferred_fetches.push(deferred_fetch);
|
||
}
|
||
|
||
/// Initiate a new fetch given a response callback.
|
||
pub(crate) fn fetch_async_with_callback(
|
||
&mut self,
|
||
load: LoadType,
|
||
request: RequestBuilder,
|
||
callback: BoxedFetchCallback,
|
||
) {
|
||
self.add_blocking_load(load);
|
||
self.fetch_async_background(request, callback);
|
||
}
|
||
|
||
/// Initiate a new fetch that does not block the document load event.
|
||
pub(crate) fn fetch_async_background(
|
||
&mut self,
|
||
request: RequestBuilder,
|
||
callback: BoxedFetchCallback,
|
||
) {
|
||
self.cancellers.push(FetchCanceller::new(
|
||
request.id,
|
||
request.keep_alive,
|
||
self.resource_threads.core_thread.clone(),
|
||
));
|
||
fetch_async(&self.resource_threads.core_thread, request, None, callback);
|
||
}
|
||
|
||
/// Mark an in-progress network request complete.
|
||
pub(crate) fn finish_load(&mut self, load: &LoadType) {
|
||
debug!(
|
||
"Removing blocking load {:?} ({}).",
|
||
load,
|
||
self.blocking_loads.len()
|
||
);
|
||
let idx = self
|
||
.blocking_loads
|
||
.iter()
|
||
.position(|unfinished| *unfinished == *load);
|
||
match idx {
|
||
Some(i) => {
|
||
self.blocking_loads.remove(i);
|
||
},
|
||
None => warn!("unknown completed load {:?}", load),
|
||
}
|
||
}
|
||
|
||
pub(crate) fn is_blocked(&self) -> bool {
|
||
// TODO: Ensure that we report blocked if parsing is still ongoing.
|
||
!self.blocking_loads.is_empty()
|
||
}
|
||
|
||
pub(crate) fn is_only_blocked_by_iframes(&self) -> bool {
|
||
self.blocking_loads
|
||
.iter()
|
||
.all(|load| matches!(*load, LoadType::Subframe(_)))
|
||
}
|
||
|
||
pub(crate) fn inhibit_events(&mut self) {
|
||
self.events_inhibited = true;
|
||
}
|
||
|
||
pub(crate) fn events_inhibited(&self) -> bool {
|
||
self.events_inhibited
|
||
}
|
||
|
||
pub(crate) fn resource_threads(&self) -> &ResourceThreads {
|
||
&self.resource_threads
|
||
}
|
||
}
|