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