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