script/dom/servoparser/
prefetch.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::ops::Deref;
7
8use base::IpcSend;
9use base::id::{PipelineId, WebViewId};
10use html5ever::buffer_queue::BufferQueue;
11use html5ever::tokenizer::states::RawKind;
12use html5ever::tokenizer::{
13    Tag, TagKind, Token, TokenSink, TokenSinkResult, Tokenizer as HtmlTokenizer,
14};
15use html5ever::{Attribute, LocalName, local_name};
16use js::jsapi::JSTracer;
17use markup5ever::TokenizerResult;
18use net_traits::policy_container::PolicyContainer;
19use net_traits::request::{
20    CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer,
21};
22use net_traits::{CoreResourceMsg, FetchChannels, ReferrerPolicy, ResourceThreads};
23use servo_url::{ImmutableOrigin, ServoUrl};
24
25use crate::dom::bindings::reflector::DomGlobal;
26use crate::dom::bindings::trace::{CustomTraceable, JSTraceable};
27use crate::dom::document::Document;
28use crate::dom::html::htmlscriptelement::script_fetch_request;
29use crate::dom::processingoptions::determine_cors_settings_for_token;
30use crate::fetch::create_a_potential_cors_request;
31use crate::script_module::ScriptFetchOptions;
32
33#[derive(JSTraceable, MallocSizeOf)]
34#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
35pub(crate) struct Tokenizer {
36    #[ignore_malloc_size_of = "Defined in html5ever"]
37    inner: TraceableTokenizer,
38}
39
40struct TraceableTokenizer(HtmlTokenizer<PrefetchSink>);
41
42impl Deref for TraceableTokenizer {
43    type Target = HtmlTokenizer<PrefetchSink>;
44    fn deref(&self) -> &Self::Target {
45        &self.0
46    }
47}
48
49#[allow(unsafe_code)]
50unsafe impl JSTraceable for TraceableTokenizer {
51    unsafe fn trace(&self, trc: *mut JSTracer) {
52        CustomTraceable::trace(&self.0, trc)
53    }
54}
55
56#[allow(unsafe_code)]
57unsafe impl CustomTraceable for PrefetchSink {
58    unsafe fn trace(&self, trc: *mut JSTracer) {
59        <Self as JSTraceable>::trace(self, trc)
60    }
61}
62
63impl Tokenizer {
64    pub(crate) fn new(document: &Document) -> Self {
65        let global = document.global();
66        let sink = PrefetchSink {
67            origin: document.origin().immutable().clone(),
68            pipeline_id: global.pipeline_id(),
69            webview_id: document.webview_id(),
70            base_url: RefCell::new(None),
71            document_url: document.url(),
72            referrer: global.get_referrer(),
73            referrer_policy: document.get_referrer_policy(),
74            resource_threads: document.loader().resource_threads().clone(),
75            // Initially we set prefetching to false, and only set it
76            // true after the first script tag, since that is what will
77            // block the main parser.
78            prefetching: Cell::new(false),
79            insecure_requests_policy: document.insecure_requests_policy(),
80            has_trustworthy_ancestor_origin: document.has_trustworthy_ancestor_or_current_origin(),
81            policy_container: global.policy_container(),
82        };
83        let options = Default::default();
84        let inner = TraceableTokenizer(HtmlTokenizer::new(sink, options));
85        Tokenizer { inner }
86    }
87
88    pub(crate) fn feed(&self, input: &BufferQueue) {
89        while let TokenizerResult::Script(PrefetchHandle) = self.inner.feed(input) {}
90    }
91}
92
93#[derive(JSTraceable)]
94struct PrefetchSink {
95    #[no_trace]
96    origin: ImmutableOrigin,
97    #[no_trace]
98    pipeline_id: PipelineId,
99    #[no_trace]
100    webview_id: WebViewId,
101    #[no_trace]
102    document_url: ServoUrl,
103    #[no_trace]
104    base_url: RefCell<Option<ServoUrl>>,
105    #[no_trace]
106    referrer: Referrer,
107    #[no_trace]
108    referrer_policy: ReferrerPolicy,
109    #[no_trace]
110    resource_threads: ResourceThreads,
111    prefetching: Cell<bool>,
112    #[no_trace]
113    insecure_requests_policy: InsecureRequestsPolicy,
114    has_trustworthy_ancestor_origin: bool,
115    #[no_trace]
116    policy_container: PolicyContainer,
117}
118
119/// The prefetch tokenizer produces trivial results
120#[derive(Clone, Copy, JSTraceable)]
121struct PrefetchHandle;
122
123impl TokenSink for PrefetchSink {
124    type Handle = PrefetchHandle;
125    fn process_token(&self, token: Token, _line_number: u64) -> TokenSinkResult<PrefetchHandle> {
126        let tag = match token {
127            Token::TagToken(ref tag) => tag,
128            _ => return TokenSinkResult::Continue,
129        };
130        match (tag.kind, &tag.name) {
131            (TagKind::StartTag, &local_name!("script")) if self.prefetching.get() => {
132                if let Some(url) = self.get_url(tag, local_name!("src")) {
133                    debug!("Prefetch script {}", url);
134                    let cors_setting = self.get_cors_settings(tag, local_name!("crossorigin"));
135                    let integrity_metadata = self
136                        .get_attr(tag, local_name!("integrity"))
137                        .map(|attr| String::from(&attr.value))
138                        .unwrap_or_default();
139                    let cryptographic_nonce = self
140                        .get_attr(tag, local_name!("nonce"))
141                        .map(|attr| String::from(&attr.value))
142                        .unwrap_or_default();
143                    let request = script_fetch_request(
144                        self.webview_id,
145                        url,
146                        cors_setting,
147                        self.origin.clone(),
148                        self.pipeline_id,
149                        ScriptFetchOptions {
150                            referrer: self.referrer.clone(),
151                            referrer_policy: self.referrer_policy,
152                            integrity_metadata,
153                            cryptographic_nonce,
154                            credentials_mode: CredentialsMode::CredentialsSameOrigin,
155                            parser_metadata: ParserMetadata::ParserInserted,
156                        },
157                        self.insecure_requests_policy,
158                        self.has_trustworthy_ancestor_origin,
159                        self.policy_container.clone(),
160                    );
161                    let _ = self
162                        .resource_threads
163                        .send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch));
164                }
165                TokenSinkResult::RawData(RawKind::ScriptData)
166            },
167            (TagKind::StartTag, &local_name!("img")) if self.prefetching.get() => {
168                if let Some(url) = self.get_url(tag, local_name!("src")) {
169                    debug!("Prefetch {} {}", tag.name, url);
170                    let request = create_a_potential_cors_request(
171                        Some(self.webview_id),
172                        url,
173                        Destination::Image,
174                        self.get_cors_settings(tag, local_name!("crossorigin")),
175                        None,
176                        self.referrer.clone(),
177                        self.insecure_requests_policy,
178                        self.has_trustworthy_ancestor_origin,
179                        self.policy_container.clone(),
180                    )
181                    .origin(self.origin.clone())
182                    .pipeline_id(Some(self.pipeline_id))
183                    .referrer_policy(self.get_referrer_policy(tag, local_name!("referrerpolicy")));
184
185                    let _ = self
186                        .resource_threads
187                        .send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch));
188                }
189                TokenSinkResult::Continue
190            },
191            (TagKind::StartTag, &local_name!("link")) if self.prefetching.get() => {
192                if let Some(rel) = self.get_attr(tag, local_name!("rel")) {
193                    if rel.value.eq_ignore_ascii_case("stylesheet") {
194                        if let Some(url) = self.get_url(tag, local_name!("href")) {
195                            debug!("Prefetch {} {}", tag.name, url);
196                            let cors_setting =
197                                self.get_cors_settings(tag, local_name!("crossorigin"));
198                            let referrer_policy =
199                                self.get_referrer_policy(tag, local_name!("referrerpolicy"));
200                            let integrity_metadata = self
201                                .get_attr(tag, local_name!("integrity"))
202                                .map(|attr| String::from(&attr.value))
203                                .unwrap_or_default();
204
205                            // https://html.spec.whatwg.org/multipage/#default-fetch-and-process-the-linked-resource
206                            let request = create_a_potential_cors_request(
207                                Some(self.webview_id),
208                                url,
209                                Destination::Style,
210                                cors_setting,
211                                None,
212                                self.referrer.clone(),
213                                self.insecure_requests_policy,
214                                self.has_trustworthy_ancestor_origin,
215                                self.policy_container.clone(),
216                            )
217                            .origin(self.origin.clone())
218                            .pipeline_id(Some(self.pipeline_id))
219                            .referrer_policy(referrer_policy)
220                            .integrity_metadata(integrity_metadata);
221
222                            let _ = self
223                                .resource_threads
224                                .send(CoreResourceMsg::Fetch(request, FetchChannels::Prefetch));
225                        }
226                    }
227                }
228                TokenSinkResult::Continue
229            },
230            (TagKind::StartTag, &local_name!("script")) => {
231                TokenSinkResult::RawData(RawKind::ScriptData)
232            },
233            (TagKind::EndTag, &local_name!("script")) => {
234                // After the first script tag, the main parser is blocked, so it's worth prefetching.
235                self.prefetching.set(true);
236                TokenSinkResult::Script(PrefetchHandle)
237            },
238            (TagKind::StartTag, &local_name!("base")) => {
239                if let Some(url) = self.get_url(tag, local_name!("href")) {
240                    if self.base_url.borrow().is_none() {
241                        debug!("Setting base {}", url);
242                        *self.base_url.borrow_mut() = Some(url);
243                    }
244                }
245                TokenSinkResult::Continue
246            },
247            _ => TokenSinkResult::Continue,
248        }
249    }
250}
251
252impl PrefetchSink {
253    fn get_attr<'a>(&'a self, tag: &'a Tag, name: LocalName) -> Option<&'a Attribute> {
254        tag.attrs.iter().find(|attr| attr.name.local == name)
255    }
256
257    fn get_url(&self, tag: &Tag, name: LocalName) -> Option<ServoUrl> {
258        let attr = self.get_attr(tag, name)?;
259        let base_url = self.base_url.borrow();
260        let base = base_url.as_ref().unwrap_or(&self.document_url);
261        ServoUrl::parse_with_base(Some(base), &attr.value).ok()
262    }
263
264    fn get_referrer_policy(&self, tag: &Tag, name: LocalName) -> ReferrerPolicy {
265        self.get_attr(tag, name)
266            .map(|attr| ReferrerPolicy::from(&*attr.value))
267            .unwrap_or(self.referrer_policy)
268    }
269
270    fn get_cors_settings(&self, tag: &Tag, name: LocalName) -> Option<CorsSettings> {
271        let attr = self.get_attr(tag, name)?;
272        determine_cors_settings_for_token(&attr.value)
273    }
274}