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