1use 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 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#[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 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 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}