mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Prefetch img and scripts during parsing
This commit is contained in:
parent
5bcb1b579c
commit
1aeb97b281
8 changed files with 351 additions and 45 deletions
185
components/script/dom/servoparser/prefetch.rs
Normal file
185
components/script/dom/servoparser/prefetch.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
/* 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 crate::dom::bindings::reflector::DomObject;
|
||||
use crate::dom::bindings::trace::JSTraceable;
|
||||
use crate::dom::document::Document;
|
||||
use crate::dom::htmlimageelement::image_fetch_request;
|
||||
use crate::dom::htmlscriptelement::script_fetch_request;
|
||||
use crate::stylesheet_loader::stylesheet_fetch_request;
|
||||
use html5ever::buffer_queue::BufferQueue;
|
||||
use html5ever::tokenizer::Tag;
|
||||
use html5ever::tokenizer::TagKind;
|
||||
use html5ever::tokenizer::Token;
|
||||
use html5ever::tokenizer::TokenSink;
|
||||
use html5ever::tokenizer::TokenSinkResult;
|
||||
use html5ever::tokenizer::Tokenizer as HtmlTokenizer;
|
||||
use html5ever::tokenizer::TokenizerResult;
|
||||
use html5ever::Attribute;
|
||||
use html5ever::LocalName;
|
||||
use js::jsapi::JSTracer;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::request::CorsSettings;
|
||||
use net_traits::request::Referrer;
|
||||
use net_traits::CoreResourceMsg;
|
||||
use net_traits::FetchChannels;
|
||||
use net_traits::IpcSend;
|
||||
use net_traits::ReferrerPolicy;
|
||||
use net_traits::ResourceThreads;
|
||||
use servo_url::ImmutableOrigin;
|
||||
use servo_url::ServoUrl;
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
#[must_root]
|
||||
pub struct Tokenizer {
|
||||
#[ignore_malloc_size_of = "Defined in html5ever"]
|
||||
inner: HtmlTokenizer<PrefetchSink>,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl JSTraceable for HtmlTokenizer<PrefetchSink> {
|
||||
unsafe fn trace(&self, trc: *mut JSTracer) {
|
||||
self.sink.trace(trc)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tokenizer {
|
||||
pub fn new(document: &Document) -> Self {
|
||||
let sink = PrefetchSink {
|
||||
origin: document.origin().immutable().clone(),
|
||||
pipeline_id: document.global().pipeline_id(),
|
||||
base: document.url(),
|
||||
referrer: Referrer::ReferrerUrl(document.url()),
|
||||
referrer_policy: document.get_referrer_policy(),
|
||||
resource_threads: document.loader().resource_threads().clone(),
|
||||
// Initially we set prefetching to false, and only set it
|
||||
// true after the first script tag, since that is what will
|
||||
// block the main parser.
|
||||
prefetching: false,
|
||||
};
|
||||
let options = Default::default();
|
||||
let inner = HtmlTokenizer::new(sink, options);
|
||||
Tokenizer { inner }
|
||||
}
|
||||
|
||||
pub fn feed(&mut self, input: &mut BufferQueue) -> TokenizerResult<()> {
|
||||
self.inner.feed(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(JSTraceable)]
|
||||
struct PrefetchSink {
|
||||
origin: ImmutableOrigin,
|
||||
pipeline_id: PipelineId,
|
||||
base: ServoUrl,
|
||||
referrer: Referrer,
|
||||
referrer_policy: Option<ReferrerPolicy>,
|
||||
resource_threads: ResourceThreads,
|
||||
prefetching: bool,
|
||||
}
|
||||
|
||||
impl TokenSink for PrefetchSink {
|
||||
type Handle = ();
|
||||
fn process_token(&mut self, token: Token, _line_number: u64) -> TokenSinkResult<()> {
|
||||
if let Token::TagToken(ref tag) = token {
|
||||
match (tag.kind, &tag.name) {
|
||||
(TagKind::StartTag, local_name!("script")) if self.prefetching => {
|
||||
if let Some(url) = self.get_url(tag, local_name!("src")) {
|
||||
debug!("Prefetch script {}", url);
|
||||
let cors_setting = self.get_cors_settings(tag, local_name!("crossorigin"));
|
||||
let integrity_metadata = self
|
||||
.get_attr(tag, local_name!("integrity"))
|
||||
.map(|attr| String::from(&attr.value))
|
||||
.unwrap_or_default();
|
||||
let request = script_fetch_request(
|
||||
url,
|
||||
cors_setting,
|
||||
self.origin.clone(),
|
||||
self.pipeline_id,
|
||||
self.referrer.clone(),
|
||||
self.referrer_policy,
|
||||
integrity_metadata,
|
||||
);
|
||||
let _ = self
|
||||
.resource_threads
|
||||
.send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch));
|
||||
}
|
||||
// Don't prefetch inside script
|
||||
self.prefetching = false;
|
||||
},
|
||||
(TagKind::StartTag, local_name!("img")) if self.prefetching => {
|
||||
if let Some(url) = self.get_url(tag, local_name!("src")) {
|
||||
debug!("Prefetch {} {}", tag.name, url);
|
||||
let request =
|
||||
image_fetch_request(url, self.origin.clone(), self.pipeline_id);
|
||||
let _ = self
|
||||
.resource_threads
|
||||
.send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch));
|
||||
}
|
||||
},
|
||||
(TagKind::StartTag, local_name!("link")) if self.prefetching => {
|
||||
if let Some(rel) = self.get_attr(tag, local_name!("rel")) {
|
||||
if rel.value.eq_ignore_ascii_case("stylesheet") {
|
||||
if let Some(url) = self.get_url(tag, local_name!("href")) {
|
||||
debug!("Prefetch {} {}", tag.name, url);
|
||||
let cors_setting =
|
||||
self.get_cors_settings(tag, local_name!("crossorigin"));
|
||||
let integrity_metadata = self
|
||||
.get_attr(tag, local_name!("integrity"))
|
||||
.map(|attr| String::from(&attr.value))
|
||||
.unwrap_or_default();
|
||||
let request = stylesheet_fetch_request(
|
||||
url,
|
||||
cors_setting,
|
||||
self.origin.clone(),
|
||||
self.pipeline_id,
|
||||
self.referrer.clone(),
|
||||
self.referrer_policy,
|
||||
integrity_metadata,
|
||||
);
|
||||
let _ = self
|
||||
.resource_threads
|
||||
.send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(TagKind::EndTag, local_name!("script")) => {
|
||||
// After the first script tag, the main parser is blocked, so it's worth prefetching.
|
||||
self.prefetching = true;
|
||||
},
|
||||
(TagKind::StartTag, local_name!("base")) => {
|
||||
if let Some(url) = self.get_url(tag, local_name!("href")) {
|
||||
debug!("Setting base {}", url);
|
||||
self.base = url;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
TokenSinkResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
impl PrefetchSink {
|
||||
fn get_attr<'a>(&'a self, tag: &'a Tag, name: LocalName) -> Option<&'a Attribute> {
|
||||
tag.attrs.iter().find(|attr| attr.name.local == name)
|
||||
}
|
||||
|
||||
fn get_url(&self, tag: &Tag, name: LocalName) -> Option<ServoUrl> {
|
||||
self.get_attr(tag, name)
|
||||
.and_then(|attr| ServoUrl::parse_with_base(Some(&self.base), &attr.value).ok())
|
||||
}
|
||||
|
||||
fn get_cors_settings(&self, tag: &Tag, name: LocalName) -> Option<CorsSettings> {
|
||||
let crossorigin = self.get_attr(tag, name)?;
|
||||
if crossorigin.value.eq_ignore_ascii_case("anonymous") {
|
||||
Some(CorsSettings::Anonymous)
|
||||
} else if crossorigin.value.eq_ignore_ascii_case("use-credentials") {
|
||||
Some(CorsSettings::UseCredentials)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue