diff options
author | Alan Jeffrey <ajeffrey@mozilla.com> | 2019-09-05 11:57:21 -0500 |
---|---|---|
committer | Alan Jeffrey <ajeffrey@mozilla.com> | 2019-09-10 16:13:49 -0500 |
commit | 1aeb97b2810606e7dd7768e9466b2857688c9b3a (patch) | |
tree | 337ab43a5e23c3e9c96c791e5178efba1acaf57a /components/script/dom/servoparser/prefetch.rs | |
parent | 5bcb1b579c28b09fa72163ac38e2864778e3dd9d (diff) | |
download | servo-1aeb97b2810606e7dd7768e9466b2857688c9b3a.tar.gz servo-1aeb97b2810606e7dd7768e9466b2857688c9b3a.zip |
Prefetch img and scripts during parsing
Diffstat (limited to 'components/script/dom/servoparser/prefetch.rs')
-rw-r--r-- | components/script/dom/servoparser/prefetch.rs | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/components/script/dom/servoparser/prefetch.rs b/components/script/dom/servoparser/prefetch.rs new file mode 100644 index 00000000000..314cb3b97a1 --- /dev/null +++ b/components/script/dom/servoparser/prefetch.rs @@ -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 + } + } +} |