1use std::borrow::Cow;
6use std::cell::Cell;
7use std::ffi::CStr;
8use std::fs::read_to_string;
9use std::path::PathBuf;
10use std::rc::Rc;
11
12use bytes::{Bytes, BytesMut};
13use dom_struct::dom_struct;
14use encoding_rs::Encoding;
15use html5ever::{LocalName, Prefix, local_name};
16use js::context::JSContext;
17use js::rust::HandleObject;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::http_status::HttpStatus;
20use net_traits::request::{
21 CorsSettings, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId,
22};
23use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
24use script_bindings::cell::DomRefCell;
25use script_bindings::domstring::{DOMString, TracedDOMString};
26use script_bindings::trace::RootedTraceableBox;
27use servo_base::id::WebViewId;
28use servo_url::ServoUrl;
29use style::attr::AttrValue;
30use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
31use stylo_atoms::Atom;
32
33use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
34use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
36use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
37use crate::dom::bindings::codegen::UnionTypes::{
38 TrustedScriptOrString, TrustedScriptURLOrUSVString,
39};
40use crate::dom::bindings::error::Fallible;
41use crate::dom::bindings::inheritance::Castable;
42use crate::dom::bindings::refcounted::Trusted;
43use crate::dom::bindings::reflector::DomGlobal;
44use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
45use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
46use crate::dom::document::Document;
47use crate::dom::domtokenlist::DOMTokenList;
48use crate::dom::element::attributes::storage::AttrRef;
49use crate::dom::element::{
50 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
51 cors_settings_attribute_credential_mode, referrer_policy_for_element,
52 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
53};
54use crate::dom::event::eventtarget::EventTarget;
55use crate::dom::globalscope::GlobalScope;
56use crate::dom::html::htmlelement::HTMLElement;
57use crate::dom::node::virtualmethods::VirtualMethods;
58use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits, UnbindContext};
59use crate::dom::performance::performanceresourcetiming::InitiatorType;
60use crate::dom::trustedtypes::trustedscript::TrustedScript;
61use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
62use crate::dom::window::Window;
63use crate::event_loop::document_loader::{LoadBlocker, LoadType};
64use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request_with_claim};
65use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
66use crate::runtime::script_runtime::IntroductionType;
67use crate::scripting::fetching_options::ScriptFetchOptions;
68use crate::scripting::import_map::{ImportMap, parse_an_import_map_string, register_import_map};
69use crate::scripting::script_execution::{ClassicScript, RethrowErrors, ScriptOptions};
70use crate::scripting::script_module::{
71 ModuleTree, fetch_an_external_module_script, fetch_inline_module_script,
72};
73use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
74
75#[dom_struct]
76pub(crate) struct HTMLScriptElement {
77 htmlelement: HTMLElement,
78
79 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
81
82 already_started: Cell<bool>,
84
85 parser_inserted: Cell<bool>,
87
88 non_blocking: Cell<bool>,
92
93 parser_document: Dom<Document>,
96
97 preparation_time_document: MutNullableDom<Document>,
100
101 line_number: u64,
103
104 script_text: DomRefCell<TracedDOMString>,
106
107 from_an_external_file: Cell<bool>,
109
110 blocking: MutNullableDom<DOMTokenList>,
112
113 marked_as_render_blocking: Cell<bool>,
116
117 result: DomRefCell<Option<TracedScriptResult>>,
119}
120
121impl HTMLScriptElement {
122 fn new_inherited(
123 local_name: LocalName,
124 prefix: Option<Prefix>,
125 document: &Document,
126 creator: ElementCreator,
127 ) -> HTMLScriptElement {
128 HTMLScriptElement {
129 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
130 already_started: Cell::new(false),
131 delaying_the_load_event: Default::default(),
132 parser_inserted: Cell::new(creator.is_parser_created()),
133 non_blocking: Cell::new(!creator.is_parser_created()),
134 parser_document: Dom::from_ref(document),
135 preparation_time_document: MutNullableDom::new(None),
136 line_number: creator.return_line_number(),
137 script_text: Default::default(),
138 from_an_external_file: Cell::new(false),
139 blocking: Default::default(),
140 marked_as_render_blocking: Default::default(),
141 result: DomRefCell::new(None),
142 }
143 }
144
145 pub(crate) fn new(
146 cx: &mut JSContext,
147 local_name: LocalName,
148 prefix: Option<Prefix>,
149 document: &Document,
150 proto: Option<HandleObject>,
151 creator: ElementCreator,
152 ) -> DomRoot<HTMLScriptElement> {
153 Node::reflect_node_with_proto(
154 cx,
155 Box::new(HTMLScriptElement::new_inherited(
156 local_name, prefix, document, creator,
157 )),
158 document,
159 proto,
160 )
161 }
162
163 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
168 debug_assert!(self.delaying_the_load_event.borrow().is_none());
169
170 *self.delaying_the_load_event.borrow_mut() =
171 Some(LoadBlocker::new(document, LoadType::Script(url)));
172 }
173
174 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
181 let element = self.upcast::<Element>();
182
183 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
184 ExternalScriptKind::Asap
185 } else if !self.parser_inserted.get() {
186 ExternalScriptKind::AsapInOrder
187 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
188 {
189 ExternalScriptKind::Deferred
190 } else {
191 ExternalScriptKind::ParsingBlocking
192 }
193 }
194
195 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
197 match script_kind {
198 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
199 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
200 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
201 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
202 }
203 }
204
205 fn finish_fetching_a_script(&self, cx: &mut JSContext, script_kind: ExternalScriptKind) {
207 let load = self
208 .result
209 .take()
210 .map(|result| result.map(Into::into))
211 .expect("Result must be ready to proceed");
212
213 match script_kind {
215 ExternalScriptKind::Asap => {
216 let document = self.preparation_time_document.get().unwrap();
217 document.asap_script_loaded(cx, self, load)
218 },
219 ExternalScriptKind::AsapInOrder => {
220 let document = self.preparation_time_document.get().unwrap();
221 document.asap_in_order_script_loaded(cx, self, load)
222 },
223 ExternalScriptKind::Deferred => {
224 let document = self.parser_document.as_rooted();
225 document.deferred_script_loaded(cx, self, load);
226 },
227 ExternalScriptKind::ParsingBlocking => {
228 let document = self.parser_document.as_rooted();
229 document.pending_parsing_blocking_script_loaded(self, load, cx);
230 },
231 }
232
233 LoadBlocker::terminate(cx, &self.delaying_the_load_event);
235 }
236}
237
238pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
241 "application/ecmascript",
242 "application/javascript",
243 "application/x-ecmascript",
244 "application/x-javascript",
245 "text/ecmascript",
246 "text/javascript",
247 "text/javascript1.0",
248 "text/javascript1.1",
249 "text/javascript1.2",
250 "text/javascript1.3",
251 "text/javascript1.4",
252 "text/javascript1.5",
253 "text/jscript",
254 "text/livescript",
255 "text/x-ecmascript",
256 "text/x-javascript",
257];
258
259#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
260pub(crate) enum ScriptType {
261 Classic,
262 Module,
263 ImportMap,
264}
265
266#[derive(JSTraceable, MallocSizeOf)]
267#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
268pub(crate) enum TracedScript {
269 Classic(Box<ClassicScript>),
270 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
271}
272
273#[expect(clippy::large_enum_variant)]
274pub(crate) enum Script {
275 Classic(RootedTraceableBox<ClassicScript>),
276 Module(RootedTraceableBox<Rc<ModuleTree>>),
277 ImportMap(Fallible<ImportMap>),
278}
279
280pub(crate) type TracedScriptResult = Result<TracedScript, ()>;
281pub(crate) type ScriptResult = Result<Script, ()>;
282
283impl From<Script> for TracedScript {
284 fn from(value: Script) -> Self {
285 match value {
286 Script::Classic(classic) => TracedScript::Classic(classic.into_box()),
287 Script::Module(module) => TracedScript::Module(*module.into_box()),
288 Script::ImportMap(_) => {
289 unreachable!("Import maps are executed immediately, traced variant is not needed")
290 },
291 }
292 }
293}
294
295impl From<TracedScript> for Script {
296 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
297 fn from(value: TracedScript) -> Self {
298 match value {
299 TracedScript::Classic(classic) => {
300 Script::Classic(RootedTraceableBox::from_box(classic))
301 },
302 TracedScript::Module(module) => Script::Module(RootedTraceableBox::new(module)),
303 }
304 }
305}
306
307struct ClassicContext {
309 elem: Trusted<HTMLScriptElement>,
311 kind: ExternalScriptKind,
313 character_encoding: &'static Encoding,
316 data: BytesMut,
318 metadata: Option<Metadata>,
320 url: UrlWithBlobClaim,
322 status: Result<(), NetworkError>,
324 fetch_options: ScriptFetchOptions,
326 response_was_cors_cross_origin: bool,
328}
329
330impl FetchResponseListener for ClassicContext {
331 fn process_request_body(&mut self, _: &GlobalScope, _: RequestId) {}
333
334 fn process_response(
335 &mut self,
336 _: &mut JSContext,
337 _: &GlobalScope,
338 _: RequestId,
339 metadata: Result<FetchMetadata, NetworkError>,
340 ) {
341 self.metadata = metadata.ok().map(|metadata| {
342 self.response_was_cors_cross_origin = metadata.is_cors_cross_origin();
343 metadata.into()
344 });
345
346 let status = self
347 .metadata
348 .as_ref()
349 .map(|m| m.status.clone())
350 .unwrap_or_else(HttpStatus::new_error);
351
352 self.status = {
353 if status.is_error() {
354 Err(NetworkError::ResourceLoadError(
355 "No http status code received".to_owned(),
356 ))
357 } else if status.is_success() {
358 Ok(())
359 } else {
360 Err(NetworkError::ResourceLoadError(format!(
361 "HTTP error code {}",
362 status.code()
363 )))
364 }
365 };
366 }
367
368 fn process_response_chunk(
369 &mut self,
370 _: &mut JSContext,
371 _: &GlobalScope,
372 _: RequestId,
373 chunk: Bytes,
374 ) {
375 if self.status.is_ok() {
376 self.data.extend_from_slice(&chunk);
377 }
378 }
379
380 fn process_response_eof(
383 mut self,
384 cx: &mut JSContext,
385 global: &GlobalScope,
386 _: RequestId,
387 response: Result<(), NetworkError>,
388 timing: ResourceFetchTiming,
389 ) {
390 network_listener::submit_timing(cx, global, &self, &response, &timing);
392
393 let elem = self.elem.root();
394
395 match (response.as_ref(), self.status.as_ref()) {
396 (Err(error), _) | (_, Err(error)) => {
397 error!(
398 "Fetching classic script failed {:?} ({:?})",
399 error, self.url
400 );
401 *elem.result.borrow_mut() = Some(Err(()));
403 elem.finish_fetching_a_script(cx, self.kind);
404 return;
405 },
406 _ => {},
407 };
408
409 let metadata = self.metadata.take().unwrap();
410 let final_url = metadata.final_url;
411
412 let encoding = metadata
415 .charset
416 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
417 .unwrap_or(self.character_encoding);
418
419 let (mut source_text, _, _) = encoding.decode(&self.data);
421
422 let global = elem.global();
423
424 if let Some(window) = global.downcast::<Window>() &&
425 let Some(script_source) = window.local_script_source()
426 {
427 substitute_with_local_script(script_source, &mut source_text, &final_url);
428 }
429
430 let mut script_options = ScriptOptions::External;
432 script_options.set(
433 ScriptOptions::MutedErrors,
434 self.response_was_cors_cross_origin,
435 );
436
437 let script = global.create_a_classic_script(
440 cx,
441 source_text,
442 final_url,
443 script_options,
444 self.fetch_options.clone(),
445 Some(IntroductionType::SRC_SCRIPT),
446 1,
447 );
448
449 let load = Script::Classic(script);
480 *elem.result.borrow_mut() = Some(Ok(load.into()));
481 elem.finish_fetching_a_script(cx, self.kind);
482 }
484
485 fn process_csp_violations(
486 &mut self,
487 cx: &mut JSContext,
488 global: &GlobalScope,
489 _request_id: RequestId,
490 violations: Vec<Violation>,
491 ) {
492 let elem = self.elem.root();
493 global.report_csp_violations(cx, violations, Some(elem.upcast()), None);
494 }
495
496 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
497 self.data.reserve(size.saturating_sub(self.data.len()));
498 }
499}
500
501impl ResourceTimingListener for ClassicContext {
502 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
503 let initiator_type =
504 InitiatorType::LocalName(self.elem.root().upcast::<Element>().local_name().clone());
505 (initiator_type, self.url.url())
506 }
507}
508
509#[allow(clippy::too_many_arguments)]
512pub(crate) fn script_fetch_request(
513 webview_id: WebViewId,
514 url: UrlWithBlobClaim,
515 cors_setting: Option<CorsSettings>,
516 options: ScriptFetchOptions,
517 referrer: Referrer,
518) -> RequestBuilder {
519 create_a_potential_cors_request_with_claim(
522 Some(webview_id),
523 url,
524 Destination::Script,
525 cors_setting,
526 None,
527 referrer,
528 )
529 .parser_metadata(options.parser_metadata)
530 .integrity_metadata(options.integrity_metadata.clone())
531 .referrer_policy(options.referrer_policy)
532 .cryptographic_nonce_metadata(options.cryptographic_nonce)
533}
534
535fn fetch_a_classic_script(
537 script: &HTMLScriptElement,
538 kind: ExternalScriptKind,
539 url: UrlWithBlobClaim,
540 cors_setting: Option<CorsSettings>,
541 options: ScriptFetchOptions,
542 character_encoding: &'static Encoding,
543) {
544 let doc = script.owner_document();
546 let global = script.global();
547 let referrer = global.get_referrer();
548 let request = script_fetch_request(
549 doc.webview_id(),
550 url.clone(),
551 cors_setting,
552 options.clone(),
553 referrer,
554 )
555 .with_global_scope(&global);
556
557 let context = ClassicContext {
560 elem: Trusted::new(script),
561 kind,
562 character_encoding,
563 data: BytesMut::new(),
564 metadata: None,
565 url,
566 status: Ok(()),
567 fetch_options: options,
568 response_was_cors_cross_origin: false,
569 };
570 doc.fetch_background(request, context);
571}
572
573impl HTMLScriptElement {
574 pub(crate) fn set_initial_script_text(&self) {
576 *self.script_text.borrow_mut() = self.text().traced();
577 }
578
579 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
581 if *self.script_text.borrow() != *self.text() {
585 let result = TrustedScript::get_trusted_type_compliant_string(
586 cx,
587 &self.owner_global(),
588 self.Text(),
589 "HTMLScriptElement text",
590 )?;
591 *self.script_text.safe_borrow_mut(cx.no_gc()) = result.traced();
592 }
593
594 Ok(())
595 }
596
597 fn has_render_blocking_attribute(&self) -> bool {
598 self.blocking
599 .get()
600 .is_some_and(|list| list.Contains(DOMString::from_static("render")))
601 }
602
603 fn potentially_render_blocking(&self, script_type: ScriptType) -> bool {
605 if self.has_render_blocking_attribute() {
609 return true;
610 }
611 let element = self.upcast::<Element>();
612 script_type == ScriptType::Classic &&
616 self.parser_inserted.get() &&
617 !element.has_attribute(&local_name!("async")) &&
618 !element.has_attribute(&local_name!("defer"))
619 }
620
621 pub(crate) fn prepare(
623 &self,
624 cx: &mut JSContext,
625 introduction_type_override: Option<&'static CStr>,
626 ) {
627 let introduction_type =
628 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
629
630 if self.already_started.get() {
632 return;
633 }
634
635 let was_parser_inserted = self.parser_inserted.get();
640 self.parser_inserted.set(false);
641
642 let element = self.upcast::<Element>();
645 let asynch = element.has_attribute(&local_name!("async"));
646 if was_parser_inserted && !asynch {
648 self.non_blocking.set(true);
649 }
650
651 if self.prepare_the_script_text(cx).is_err() {
654 return;
655 }
656 let text: Cow<'_, str> = Cow::Owned(String::from(self.script_text.borrow().str()));
658 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
660 return;
661 }
662
663 if !self.upcast::<Node>().is_connected() {
665 return;
666 }
667
668 let Some(script_type) = self.script_type() else {
669 return;
671 };
672
673 if was_parser_inserted {
677 self.parser_inserted.set(true);
678 self.non_blocking.set(false);
679 }
680
681 self.already_started.set(true);
683
684 let doc = self.owner_document();
686 self.preparation_time_document.set(Some(&doc));
687
688 if self.parser_inserted.get() && *self.parser_document != *doc {
692 return;
693 }
694
695 if !doc.scripting_enabled() {
697 return;
698 }
699
700 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
702 return;
703 }
704
705 let global = &doc.global();
706
707 if !element.has_attribute(&local_name!("src")) &&
709 global
710 .get_csp_list()
711 .should_elements_inline_type_behavior_be_blocked(
712 cx,
713 global,
714 element,
715 InlineCheckType::Script,
716 &text,
717 self.line_number as u32,
718 )
719 {
720 warn!("Blocking inline script due to CSP");
721 return;
722 }
723
724 if script_type == ScriptType::Classic {
726 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
727 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
728 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
729 let for_value = for_attribute.trim_matches(HTML_SPACE_CHARACTERS);
730 if !for_value.eq_ignore_ascii_case("window") {
731 return;
732 }
733
734 let event_value = event_attribute.trim_matches(HTML_SPACE_CHARACTERS);
735 if !event_value.eq_ignore_ascii_case("onload") &&
736 !event_value.eq_ignore_ascii_case("onload()")
737 {
738 return;
739 }
740 }
741 }
742
743 let encoding = element
748 .get_attribute_string_value(&local_name!("charset"))
749 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
750 .unwrap_or_else(|| doc.encoding());
751
752 let cors_setting = cors_setting_for_element(element);
754
755 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
757
758 let cryptographic_nonce =
763 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
764 element.nonce_value().trim().to_owned()
765 } else {
766 String::new()
767 };
768
769 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
772 let integrity_val_is_none = integrity_val.is_none();
773 let integrity_metadata = integrity_val.unwrap_or_default();
774
775 let referrer_policy = referrer_policy_for_element(element);
777
778 let parser_metadata = if self.parser_inserted.get() {
783 ParserMetadata::ParserInserted
784 } else {
785 ParserMetadata::NotParserInserted
786 };
787
788 let mut script_fetch_options = ScriptFetchOptions {
790 cryptographic_nonce,
791 integrity_metadata,
792 parser_metadata,
793 referrer_policy,
794 credentials_mode: module_credentials_mode,
795 render_blocking: false,
796 };
797
798 let base_url = doc.base_url();
801
802 let kind = self.get_script_kind(script_type);
803 let delayed_document = self.get_script_active_document(kind);
804
805 let potentially_render_blocking = self.potentially_render_blocking(script_type);
806
807 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
810 if script_type == ScriptType::ImportMap {
812 self.queue_error_event();
815 return;
816 }
817
818 if src.is_empty() {
820 self.queue_error_event();
821 return;
822 }
823
824 self.from_an_external_file.set(true);
826
827 let url = match base_url.join(&src) {
829 Ok(url) => url,
830 Err(_) => {
831 warn!("error parsing URL for script {}", src);
832 self.queue_error_event();
833 return;
834 },
835 };
836 let url = ensure_blob_referenced_by_url_is_kept_alive(global, url);
837
838 if potentially_render_blocking && doc.allows_adding_render_blocking_elements() {
840 self.marked_as_render_blocking.set(true);
841 doc.increment_render_blocking_element_count();
842 }
843
844 self.delay_load_event(&delayed_document, url.url());
846
847 if self.marked_as_render_blocking.get() {
849 script_fetch_options.render_blocking = true;
850 }
851
852 match script_type {
854 ScriptType::Classic => {
855 fetch_a_classic_script(
857 self,
858 kind,
859 url,
860 cors_setting,
861 script_fetch_options,
862 encoding,
863 );
864 },
865 ScriptType::Module => {
866 if integrity_val_is_none {
869 script_fetch_options.integrity_metadata = global
870 .import_map()
871 .resolve_a_module_integrity_metadata(&url.url());
872 }
873
874 let script = DomRoot::from_ref(self);
875
876 fetch_an_external_module_script(
878 cx,
879 url,
880 global,
881 script_fetch_options,
882 move |cx, module_tree| {
883 let load = module_tree
884 .map(|module| Script::Module(RootedTraceableBox::new(module)))
885 .ok_or(());
886 *script.result.borrow_mut() = Some(load.map(Into::into));
887
888 script.finish_fetching_a_script(cx, kind);
889 },
890 );
891 },
892 ScriptType::ImportMap => (),
893 }
894 } else {
895 assert!(!text.is_empty());
898
899 match script_type {
901 ScriptType::Classic => {
902 let script = self.global().create_a_classic_script(
905 cx,
906 text,
907 base_url,
908 ScriptOptions::empty(),
909 script_fetch_options,
910 introduction_type,
911 self.line_number as u32,
912 );
913 let result = Ok(Script::Classic(script));
914
915 if was_parser_inserted &&
916 doc.get_current_parser()
917 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
918 doc.has_a_stylesheet_that_is_blocking_scripts()
919 {
920 doc.set_pending_parsing_blocking_script(self, Some(result));
922 } else {
923 self.execute(cx, result);
925 }
926 return;
927 },
928 ScriptType::Module => {
929 self.delay_load_event(&delayed_document, base_url.clone());
931
932 if potentially_render_blocking && doc.allows_adding_render_blocking_elements() {
934 self.marked_as_render_blocking.set(true);
936 doc.increment_render_blocking_element_count();
937
938 script_fetch_options.render_blocking = true;
940 }
941
942 let script = DomRoot::from_ref(self);
943 let module = fetch_inline_module_script(
946 cx,
947 global,
948 text,
949 base_url,
950 script_fetch_options,
951 self.line_number as u32,
952 introduction_type,
953 move |_, module_tree| {
954 if module_tree.is_none() {
955 *script.result.borrow_mut() = Some(Err(()));
956 }
957 let trusted = Trusted::new(&*script);
958
959 script
961 .owner_global()
962 .task_manager()
963 .networking_task_source()
964 .queue(task!(terminate_module_fetch: move |cx| {
965 let element = trusted.root();
967 element.finish_fetching_a_script(cx, kind);
968 }));
969 },
970 );
971
972 *self.result.borrow_mut() = Some(Ok(TracedScript::Module(*module.into_box())));
973 },
974 ScriptType::ImportMap => {
975 let import_map_result = parse_an_import_map_string(cx, global, &text, base_url);
978 let script = Script::ImportMap(import_map_result);
979
980 self.execute(cx, Ok(script));
982 return;
983 },
984 }
985 }
986
987 match kind {
989 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
990 ExternalScriptKind::ParsingBlocking => {
991 delayed_document.set_pending_parsing_blocking_script(self, None);
992 },
993 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
994 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
995 }
996 }
997
998 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
1000 let doc = self.owner_document();
1002
1003 if *doc != *self.preparation_time_document.get().unwrap() {
1005 return;
1006 }
1007
1008 if self.marked_as_render_blocking.replace(false) {
1010 self.marked_as_render_blocking.set(false);
1011 doc.decrement_render_blocking_element_count();
1012 }
1013
1014 let script = match result {
1015 Err(_) => {
1017 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1018 return;
1019 },
1020
1021 Ok(script) => script,
1022 };
1023
1024 let neutralized_doc =
1028 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
1029 let doc = self.owner_document();
1030 doc.incr_ignore_destructive_writes_counter();
1031 Some(doc)
1032 } else {
1033 None
1034 };
1035
1036 let document = self.owner_document();
1037
1038 match script {
1039 Script::Classic(script) => {
1040 let old_script = document.GetCurrentScript();
1042
1043 if self.upcast::<Node>().is_in_a_shadow_tree() {
1046 document.set_current_script(None)
1047 } else {
1048 document.set_current_script(Some(self))
1049 }
1050
1051 _ = self.owner_global().run_a_classic_script(
1053 cx,
1054 script,
1055 RethrowErrors::No,
1056 None, );
1058
1059 document.set_current_script(old_script.as_deref());
1061 },
1062 Script::Module(module_tree) => {
1063 document.set_current_script(None);
1065
1066 self.owner_global()
1068 .run_a_module_script(cx, module_tree, false);
1069 },
1070 Script::ImportMap(import_map) => {
1071 register_import_map(cx, &self.owner_global(), import_map);
1073 },
1074 }
1075
1076 if let Some(doc) = neutralized_doc {
1079 doc.decr_ignore_destructive_writes_counter();
1080 }
1081
1082 if self.from_an_external_file.get() {
1084 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1085 }
1086 }
1087
1088 pub(crate) fn queue_error_event(&self) {
1089 self.owner_global()
1090 .task_manager()
1091 .dom_manipulation_task_source()
1092 .queue_simple_event(self.upcast(), atom!("error"));
1093 }
1094
1095 fn script_type(&self) -> Option<ScriptType> {
1097 let element = self.upcast::<Element>();
1098
1099 let type_attr = element.get_attribute_string_ref(&local_name!("type"));
1100 let language_attr = element.get_attribute_string_ref(&local_name!("language"));
1101
1102 match (type_attr, language_attr) {
1103 (Some(type_attribute), _) if type_attribute.is_empty() => Some(ScriptType::Classic),
1109 (None, Some(lang)) if lang.is_empty() => Some(ScriptType::Classic),
1110 (None, None) => Some(ScriptType::Classic),
1111 (None, Some(lang)) => {
1114 let language_value = lang.as_attr_ref().value();
1115 let script_block_type_string = format!("text/{}", language_value.as_ref());
1116
1117 let is_a_javascript_mime = SCRIPT_JS_MIMES
1121 .iter()
1122 .any(|mime| script_block_type_string.eq_ignore_ascii_case(mime));
1123
1124 is_a_javascript_mime.then_some(ScriptType::Classic)
1125 },
1126 (Some(ty), _) => {
1129 if ty.eq_ignore_ascii_case("module") {
1132 return Some(ScriptType::Module);
1133 }
1134
1135 if ty.eq_ignore_ascii_case("importmap") {
1138 return Some(ScriptType::ImportMap);
1139 }
1140
1141 let type_attr_value = ty.as_attr_ref().value();
1142
1143 let is_a_javascript_mime = SCRIPT_JS_MIMES.iter().any(|mime| {
1147 type_attr_value
1148 .trim_matches(HTML_SPACE_CHARACTERS)
1149 .eq_ignore_ascii_case(mime)
1150 });
1151
1152 is_a_javascript_mime.then_some(ScriptType::Classic)
1153 },
1154 }
1155 }
1156
1157 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1158 self.parser_inserted.set(parser_inserted);
1159 }
1160
1161 pub(crate) fn set_already_started(&self, already_started: bool) {
1162 self.already_started.set(already_started);
1163 }
1164
1165 fn text(&self) -> DOMString {
1166 match self.Text() {
1167 TrustedScriptOrString::String(value) => value,
1168 TrustedScriptOrString::TrustedScript(trusted_script) => {
1169 DOMString::from(trusted_script.to_string())
1170 },
1171 }
1172 }
1173}
1174
1175impl VirtualMethods for HTMLScriptElement {
1176 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1177 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1178 }
1179
1180 fn attribute_mutated(
1181 &self,
1182 cx: &mut JSContext,
1183 attr: AttrRef<'_>,
1184 mutation: AttributeMutation,
1185 ) {
1186 self.super_type()
1187 .unwrap()
1188 .attribute_mutated(cx, attr, mutation);
1189 if *attr.local_name() == local_name!("src") {
1190 if let AttributeMutation::Set(_) = mutation &&
1191 !self.parser_inserted.get() &&
1192 self.upcast::<Node>().is_connected()
1193 {
1194 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1195 }
1196 } else if *attr.local_name() == local_name!("blocking") &&
1197 !self.has_render_blocking_attribute() &&
1198 self.marked_as_render_blocking.replace(false)
1199 {
1200 let document = self.owner_document();
1201 document.decrement_render_blocking_element_count();
1202 }
1203 }
1204
1205 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1207 if let Some(s) = self.super_type() {
1208 s.children_changed(cx, mutation);
1209 }
1210
1211 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1212 let script = DomRoot::from_ref(self);
1213 self.owner_document().add_delayed_task(
1217 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1218 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1219 }),
1220 );
1221 }
1222 }
1223
1224 fn post_connection_steps(&self, cx: &mut JSContext) {
1226 if let Some(s) = self.super_type() {
1227 s.post_connection_steps(cx);
1228 }
1229
1230 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1231 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1232 }
1233 }
1234
1235 fn cloning_steps(
1236 &self,
1237 cx: &mut JSContext,
1238 copy: &Node,
1239 maybe_doc: Option<&Document>,
1240 clone_children: CloneChildrenFlag,
1241 ) {
1242 if let Some(s) = self.super_type() {
1243 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1244 }
1245
1246 if self.already_started.get() {
1248 copy.downcast::<HTMLScriptElement>()
1249 .unwrap()
1250 .set_already_started(true);
1251 }
1252 }
1253
1254 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1255 self.super_type().unwrap().unbind_from_tree(cx, context);
1256
1257 if self.marked_as_render_blocking.replace(false) {
1258 let document = self.owner_document();
1259 document.decrement_render_blocking_element_count();
1260 }
1261 }
1262}
1263
1264impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1265 fn Src(&self) -> TrustedScriptURLOrUSVString {
1267 let element = self.upcast::<Element>();
1268 element.get_trusted_type_url_attribute(&local_name!("src"))
1269 }
1270
1271 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1273 let element = self.upcast::<Element>();
1274 let local_name = &local_name!("src");
1275 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1276 cx,
1277 &element.owner_global(),
1278 value,
1279 &format!("HTMLScriptElement {}", local_name),
1280 )?;
1281 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1282 Ok(())
1283 }
1284
1285 make_getter!(Type, "type");
1287 make_setter!(SetType, "type");
1289
1290 make_getter!(Charset, "charset");
1292 make_setter!(SetCharset, "charset");
1294
1295 fn Async(&self) -> bool {
1297 self.non_blocking.get() ||
1298 self.upcast::<Element>()
1299 .has_attribute(&local_name!("async"))
1300 }
1301
1302 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1304 self.non_blocking.set(false);
1305 self.upcast::<Element>()
1306 .set_bool_attribute(cx, &local_name!("async"), value);
1307 }
1308
1309 make_bool_getter!(Defer, "defer");
1311 make_bool_setter!(SetDefer, "defer");
1313
1314 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1316 self.blocking.or_init(|| {
1317 DOMTokenList::new(
1318 cx,
1319 self.upcast(),
1320 &local_name!("blocking"),
1321 Some(vec![Atom::from("render")]),
1322 )
1323 })
1324 }
1325
1326 make_bool_getter!(NoModule, "nomodule");
1328 make_bool_setter!(SetNoModule, "nomodule");
1330
1331 make_getter!(Integrity, "integrity");
1333 make_setter!(SetIntegrity, "integrity");
1335
1336 make_getter!(Event, "event");
1338 make_setter!(SetEvent, "event");
1340
1341 make_getter!(HtmlFor, "for");
1343 make_setter!(SetHtmlFor, "for");
1345
1346 fn GetCrossOrigin(&self) -> Option<DOMString> {
1348 reflect_cross_origin_attribute(self.upcast::<Element>())
1349 }
1350
1351 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1353 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1354 }
1355
1356 fn ReferrerPolicy(&self) -> DOMString {
1358 reflect_referrer_policy_attribute(self.upcast::<Element>())
1359 }
1360
1361 make_setter!(SetReferrerPolicy, "referrerpolicy");
1363
1364 fn InnerText(&self) -> TrustedScriptOrString {
1366 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1368 }
1369
1370 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1372 let value = TrustedScript::get_trusted_type_compliant_string(
1375 cx,
1376 &self.owner_global(),
1377 input,
1378 "HTMLScriptElement innerText",
1379 )?;
1380 *self.script_text.safe_borrow_mut(cx.no_gc()) = value.clone().traced();
1381 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1383 Ok(())
1384 }
1385
1386 fn Text(&self) -> TrustedScriptOrString {
1388 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1389 }
1390
1391 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1393 let value = TrustedScript::get_trusted_type_compliant_string(
1396 cx,
1397 &self.owner_global(),
1398 value,
1399 "HTMLScriptElement text",
1400 )?;
1401 *self.script_text.safe_borrow_mut(cx.no_gc()) = value.clone().traced();
1403 Node::string_replace_all(cx, value, self.upcast::<Node>());
1405 Ok(())
1406 }
1407
1408 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1410 Some(TrustedScriptOrString::String(
1412 self.upcast::<Node>().GetTextContent()?,
1413 ))
1414 }
1415
1416 fn SetTextContent(
1418 &self,
1419 cx: &mut JSContext,
1420 value: Option<TrustedScriptOrString>,
1421 ) -> Fallible<()> {
1422 let value = TrustedScript::get_trusted_type_compliant_string(
1425 cx,
1426 &self.owner_global(),
1427 value.unwrap_or(TrustedScriptOrString::String(DOMString::new())),
1428 "HTMLScriptElement textContent",
1429 )?;
1430 *self.script_text.safe_borrow_mut(cx.no_gc()) = value.clone().traced();
1432 self.upcast::<Node>()
1434 .set_text_content_for_element(cx, Some(value));
1435 Ok(())
1436 }
1437
1438 fn Supports(_window: &Window, type_: DOMString) -> bool {
1440 matches!(&*type_.str(), "classic" | "module" | "importmap")
1443 }
1444}
1445
1446pub fn substitute_with_local_script(
1447 script_source: &str,
1448 script: &mut Cow<'_, str>,
1449 url: &ServoUrl,
1450) {
1451 let mut path = PathBuf::from(script_source);
1452 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1453 debug!("Attempting to read script stored at: {:?}", path);
1454 match read_to_string(path.clone()) {
1455 Ok(local_script) => {
1456 debug!("Found script stored at: {:?}", path);
1457 *script = Cow::Owned(local_script);
1458 },
1459 Err(why) => warn!("Could not restore script from file {:?}", why),
1460 }
1461}
1462
1463#[derive(Clone, Copy)]
1464enum ExternalScriptKind {
1465 Deferred,
1466 ParsingBlocking,
1467 AsapInOrder,
1468 Asap,
1469}