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 dom_struct::dom_struct;
13use encoding_rs::Encoding;
14use html5ever::{LocalName, Prefix, local_name};
15use js::context::JSContext;
16use js::rust::{HandleObject, Stencil};
17use net_traits::http_status::HttpStatus;
18use net_traits::request::{
19 CorsSettings, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId,
20};
21use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
22use servo_base::id::WebViewId;
23use servo_url::ServoUrl;
24use style::attr::AttrValue;
25use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
26use stylo_atoms::Atom;
27use uuid::Uuid;
28
29use crate::document_loader::{LoadBlocker, LoadType};
30use crate::dom::attr::Attr;
31use crate::dom::bindings::cell::DomRefCell;
32use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
33use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
34use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
35use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
36use crate::dom::bindings::codegen::UnionTypes::{
37 TrustedScriptOrString, TrustedScriptURLOrUSVString,
38};
39use crate::dom::bindings::error::{Error, Fallible};
40use crate::dom::bindings::inheritance::Castable;
41use crate::dom::bindings::refcounted::Trusted;
42use crate::dom::bindings::reflector::DomGlobal;
43use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
44use crate::dom::bindings::str::DOMString;
45use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
46use crate::dom::document::Document;
47use crate::dom::domtokenlist::DOMTokenList;
48use crate::dom::element::{
49 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
50 cors_settings_attribute_credential_mode, referrer_policy_for_element,
51 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
52};
53use crate::dom::event::{Event, EventBubbles, EventCancelable};
54use crate::dom::global_scope_script_execution::{ClassicScript, ErrorReporting, RethrowErrors};
55use crate::dom::globalscope::GlobalScope;
56use crate::dom::html::htmlelement::HTMLElement;
57use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits, UnbindContext};
58use crate::dom::performance::performanceresourcetiming::InitiatorType;
59use crate::dom::trustedtypes::trustedscript::TrustedScript;
60use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
61use crate::dom::virtualmethods::VirtualMethods;
62use crate::dom::window::Window;
63use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
64use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
65use crate::script_module::{
66 ImportMap, ModuleOwner, ModuleTree, ScriptFetchOptions, fetch_an_external_module_script,
67 fetch_inline_module_script, parse_an_import_map_string, register_import_map,
68};
69use crate::script_runtime::{CanGc, IntroductionType};
70
71#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq, MallocSizeOf)]
73pub(crate) struct ScriptId(#[no_trace] Uuid);
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 #[ignore_malloc_size_of = "Defined in uuid"]
106 id: ScriptId,
107
108 script_text: DomRefCell<DOMString>,
110
111 from_an_external_file: Cell<bool>,
113
114 blocking: MutNullableDom<DOMTokenList>,
116
117 marked_as_render_blocking: Cell<bool>,
120}
121
122impl HTMLScriptElement {
123 fn new_inherited(
124 local_name: LocalName,
125 prefix: Option<Prefix>,
126 document: &Document,
127 creator: ElementCreator,
128 ) -> HTMLScriptElement {
129 HTMLScriptElement {
130 id: ScriptId(Uuid::new_v4()),
131 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
132 already_started: Cell::new(false),
133 delaying_the_load_event: Default::default(),
134 parser_inserted: Cell::new(creator.is_parser_created()),
135 non_blocking: Cell::new(!creator.is_parser_created()),
136 parser_document: Dom::from_ref(document),
137 preparation_time_document: MutNullableDom::new(None),
138 line_number: creator.return_line_number(),
139 script_text: DomRefCell::new(DOMString::new()),
140 from_an_external_file: Cell::new(false),
141 blocking: Default::default(),
142 marked_as_render_blocking: Default::default(),
143 }
144 }
145
146 pub(crate) fn new(
147 cx: &mut js::context::JSContext,
148 local_name: LocalName,
149 prefix: Option<Prefix>,
150 document: &Document,
151 proto: Option<HandleObject>,
152 creator: ElementCreator,
153 ) -> DomRoot<HTMLScriptElement> {
154 Node::reflect_node_with_proto(
155 cx,
156 Box::new(HTMLScriptElement::new_inherited(
157 local_name, prefix, document, creator,
158 )),
159 document,
160 proto,
161 )
162 }
163
164 pub(crate) fn get_script_id(&self) -> ScriptId {
165 self.id
166 }
167
168 pub(crate) fn delay_load_event(&self, url: ServoUrl) {
176 let document = self.get_script_active_document();
177
178 let blocker = &self.delaying_the_load_event;
179 if blocker.borrow().is_none() {
180 *blocker.borrow_mut() = Some(LoadBlocker::new(&document, LoadType::Script(url)));
181 }
182 }
183
184 pub(crate) fn get_script_kind(&self) -> ExternalScriptKind {
191 let element = self.upcast::<Element>();
192 let was_parser_inserted = self.parser_inserted.get();
193 let asynch = element.has_attribute(&local_name!("async"));
194 let mut script_kind = ExternalScriptKind::Asap;
195
196 match self.get_script_type() {
197 Some(ScriptType::Classic) => {
198 if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !asynch {
199 script_kind = ExternalScriptKind::Deferred
200 } else if was_parser_inserted && !asynch {
201 script_kind = ExternalScriptKind::ParsingBlocking
202 } else if !asynch && !self.non_blocking.get() {
203 script_kind = ExternalScriptKind::AsapInOrder
204 }
205 },
206 Some(ScriptType::Module) => {
207 if !asynch && was_parser_inserted {
208 script_kind = ExternalScriptKind::Deferred
209 } else if !asynch && !self.non_blocking.get() {
210 script_kind = ExternalScriptKind::AsapInOrder
211 }
212 },
213 Some(ScriptType::ImportMap) => (),
214 None => (),
215 }
216
217 script_kind
218 }
219
220 pub(crate) fn get_script_active_document(&self) -> DomRoot<Document> {
222 let script_kind = self.get_script_kind();
223 match script_kind {
224 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
225 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
226 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
227 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
228 }
229 }
230}
231
232pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
235 "application/ecmascript",
236 "application/javascript",
237 "application/x-ecmascript",
238 "application/x-javascript",
239 "text/ecmascript",
240 "text/javascript",
241 "text/javascript1.0",
242 "text/javascript1.1",
243 "text/javascript1.2",
244 "text/javascript1.3",
245 "text/javascript1.4",
246 "text/javascript1.5",
247 "text/jscript",
248 "text/livescript",
249 "text/x-ecmascript",
250 "text/x-javascript",
251];
252
253#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
254pub(crate) enum ScriptType {
255 Classic,
256 Module,
257 ImportMap,
258}
259
260#[derive(JSTraceable, MallocSizeOf)]
261pub(crate) struct CompiledSourceCode {
262 #[ignore_malloc_size_of = "SM handles JS values"]
263 pub(crate) source_code: Stencil,
264 #[conditional_malloc_size_of = "Rc is hard"]
265 pub(crate) original_text: Rc<DOMString>,
266}
267
268#[derive(JSTraceable, MallocSizeOf)]
269pub(crate) enum SourceCode {
270 Text(#[conditional_malloc_size_of] Rc<DOMString>),
271 Compiled(CompiledSourceCode),
272}
273
274#[derive(JSTraceable, MallocSizeOf)]
275pub(crate) struct ScriptOrigin {
276 pub code: SourceCode,
277 #[no_trace]
278 pub url: ServoUrl,
279 external: bool,
280 pub fetch_options: ScriptFetchOptions,
281 type_: ScriptType,
282 unminified_dir: Option<String>,
283 import_map: Fallible<ImportMap>,
284}
285
286impl ScriptOrigin {
287 pub(crate) fn internal(
288 text: Rc<DOMString>,
289 url: ServoUrl,
290 fetch_options: ScriptFetchOptions,
291 type_: ScriptType,
292 unminified_dir: Option<String>,
293 import_map: Fallible<ImportMap>,
294 ) -> ScriptOrigin {
295 ScriptOrigin {
296 code: SourceCode::Text(text),
297 url,
298 external: false,
299 fetch_options,
300 type_,
301 unminified_dir,
302 import_map,
303 }
304 }
305
306 pub(crate) fn external(
307 text: Rc<DOMString>,
308 url: ServoUrl,
309 fetch_options: ScriptFetchOptions,
310 type_: ScriptType,
311 unminified_dir: Option<String>,
312 ) -> ScriptOrigin {
313 ScriptOrigin {
314 code: SourceCode::Text(text),
315 url,
316 external: true,
317 fetch_options,
318 type_,
319 unminified_dir,
320 import_map: Err(Error::NotFound(None)),
321 }
322 }
323
324 pub(crate) fn text(&self) -> Rc<DOMString> {
325 match &self.code {
326 SourceCode::Text(text) => Rc::clone(text),
327 SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text),
328 }
329 }
330}
331
332pub(crate) fn finish_fetching_a_script(
334 elem: &HTMLScriptElement,
335 script_kind: ExternalScriptKind,
336 load: ScriptResult,
337 cx: &mut js::context::JSContext,
338) {
339 let document;
342
343 match script_kind {
344 ExternalScriptKind::Asap => {
345 document = elem.preparation_time_document.get().unwrap();
346 document.asap_script_loaded(cx, elem, load)
347 },
348 ExternalScriptKind::AsapInOrder => {
349 document = elem.preparation_time_document.get().unwrap();
350 document.asap_in_order_script_loaded(cx, elem, load)
351 },
352 ExternalScriptKind::Deferred => {
353 document = elem.parser_document.as_rooted();
354 document.deferred_script_loaded(cx, elem, load);
355 },
356 ExternalScriptKind::ParsingBlocking => {
357 document = elem.parser_document.as_rooted();
358 document.pending_parsing_blocking_script_loaded(elem, load, cx);
359 },
360 }
361
362 LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
365}
366
367pub(crate) type ScriptResult = Result<Script, ()>;
368
369#[derive(JSTraceable, MallocSizeOf)]
371#[expect(clippy::large_enum_variant)]
372pub(crate) enum Script {
373 Classic(ClassicScript),
374 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
375 ImportMap(ScriptOrigin),
376}
377
378struct ClassicContext {
380 elem: Trusted<HTMLScriptElement>,
382 kind: ExternalScriptKind,
384 character_encoding: &'static Encoding,
387 data: Vec<u8>,
389 metadata: Option<Metadata>,
391 url: ServoUrl,
393 status: Result<(), NetworkError>,
395 fetch_options: ScriptFetchOptions,
397 response_was_cors_cross_origin: bool,
399}
400
401impl FetchResponseListener for ClassicContext {
402 fn process_request_body(&mut self, _: RequestId) {}
404
405 fn process_response(
406 &mut self,
407 _: &mut js::context::JSContext,
408 _: RequestId,
409 metadata: Result<FetchMetadata, NetworkError>,
410 ) {
411 self.metadata = metadata.ok().map(|meta| {
412 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
413 match meta {
414 FetchMetadata::Unfiltered(m) => m,
415 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
416 }
417 });
418
419 let status = self
420 .metadata
421 .as_ref()
422 .map(|m| m.status.clone())
423 .unwrap_or_else(HttpStatus::new_error);
424
425 self.status = {
426 if status.is_error() {
427 Err(NetworkError::ResourceLoadError(
428 "No http status code received".to_owned(),
429 ))
430 } else if status.is_success() {
431 Ok(())
432 } else {
433 Err(NetworkError::ResourceLoadError(format!(
434 "HTTP error code {}",
435 status.code()
436 )))
437 }
438 };
439 }
440
441 fn process_response_chunk(
442 &mut self,
443 _: &mut js::context::JSContext,
444 _: RequestId,
445 mut chunk: Vec<u8>,
446 ) {
447 if self.status.is_ok() {
448 self.data.append(&mut chunk);
449 }
450 }
451
452 fn process_response_eof(
455 mut self,
456 cx: &mut js::context::JSContext,
457 _: RequestId,
458 response: Result<(), NetworkError>,
459 timing: ResourceFetchTiming,
460 ) {
461 match (response.as_ref(), self.status.as_ref()) {
462 (Err(error), _) | (_, Err(error)) => {
463 error!("Fetching classic script failed {:?}", error);
464 finish_fetching_a_script(&self.elem.root(), self.kind, Err(()), cx);
466
467 network_listener::submit_timing(cx, &self, &response, &timing);
469 return;
470 },
471 _ => {},
472 };
473
474 let metadata = self.metadata.take().unwrap();
475 let final_url = metadata.final_url;
476
477 let encoding = metadata
480 .charset
481 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
482 .unwrap_or(self.character_encoding);
483
484 let (mut source_text, _, _) = encoding.decode(&self.data);
486
487 let elem = self.elem.root();
488 let global = elem.global();
489
490 if let Some(window) = global.downcast::<Window>() {
491 substitute_with_local_script(window, &mut source_text, final_url.clone());
492 }
493
494 let muted_errors = self.response_was_cors_cross_origin;
496
497 let script = global.create_a_classic_script(
500 cx,
501 source_text,
502 final_url,
503 self.fetch_options.clone(),
504 ErrorReporting::from(muted_errors),
505 Some(IntroductionType::SRC_SCRIPT),
506 1,
507 true,
508 );
509
510 let load = Script::Classic(script);
541 finish_fetching_a_script(&elem, self.kind, Ok(load), cx);
542 network_listener::submit_timing(cx, &self, &response, &timing);
545 }
546
547 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
548 let global = &self.resource_timing_global();
549 let elem = self.elem.root();
550 global.report_csp_violations(violations, Some(elem.upcast()), None);
551 }
552}
553
554impl ResourceTimingListener for ClassicContext {
555 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
556 let initiator_type = InitiatorType::LocalName(
557 self.elem
558 .root()
559 .upcast::<Element>()
560 .local_name()
561 .to_string(),
562 );
563 (initiator_type, self.url.clone())
564 }
565
566 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
567 self.elem.root().owner_document().global()
568 }
569}
570
571#[allow(clippy::too_many_arguments)]
574pub(crate) fn script_fetch_request(
575 webview_id: WebViewId,
576 url: ServoUrl,
577 cors_setting: Option<CorsSettings>,
578 options: ScriptFetchOptions,
579 referrer: Referrer,
580) -> RequestBuilder {
581 create_a_potential_cors_request(
584 Some(webview_id),
585 url,
586 Destination::Script,
587 cors_setting,
588 None,
589 referrer,
590 )
591 .parser_metadata(options.parser_metadata)
592 .integrity_metadata(options.integrity_metadata.clone())
593 .referrer_policy(options.referrer_policy)
594 .cryptographic_nonce_metadata(options.cryptographic_nonce)
595}
596
597fn fetch_a_classic_script(
599 script: &HTMLScriptElement,
600 kind: ExternalScriptKind,
601 url: ServoUrl,
602 cors_setting: Option<CorsSettings>,
603 options: ScriptFetchOptions,
604 character_encoding: &'static Encoding,
605) {
606 let doc = script.owner_document();
608 let global = script.global();
609 let referrer = global.get_referrer();
610 let request = script_fetch_request(
611 doc.webview_id(),
612 url.clone(),
613 cors_setting,
614 options.clone(),
615 referrer,
616 )
617 .with_global_scope(&global);
618
619 let context = ClassicContext {
622 elem: Trusted::new(script),
623 kind,
624 character_encoding,
625 data: vec![],
626 metadata: None,
627 url,
628 status: Ok(()),
629 fetch_options: options,
630 response_was_cors_cross_origin: false,
631 };
632 doc.fetch_background(request, context);
633}
634
635impl HTMLScriptElement {
636 pub(crate) fn set_initial_script_text(&self) {
638 *self.script_text.borrow_mut() = self.text();
639 }
640
641 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
643 if self.script_text.borrow().clone() != self.text() {
647 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
648 cx,
649 &self.owner_global(),
650 self.Text(),
651 "HTMLScriptElement text",
652 )?;
653 }
654
655 Ok(())
656 }
657
658 fn has_render_blocking_attribute(&self) -> bool {
659 self.blocking
660 .get()
661 .is_some_and(|list| list.Contains("render".into()))
662 }
663
664 fn potentially_render_blocking(&self) -> bool {
666 if self.has_render_blocking_attribute() {
670 return true;
671 }
672 let element = self.upcast::<Element>();
673 self.get_script_type()
677 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
678 self.parser_inserted.get() &&
679 !element.has_attribute(&local_name!("async")) &&
680 !element.has_attribute(&local_name!("defer"))
681 }
682
683 pub(crate) fn prepare(
685 &self,
686 cx: &mut JSContext,
687 introduction_type_override: Option<&'static CStr>,
688 ) {
689 let introduction_type =
690 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
691
692 if self.already_started.get() {
694 return;
695 }
696
697 let was_parser_inserted = self.parser_inserted.get();
702 self.parser_inserted.set(false);
703
704 let element = self.upcast::<Element>();
707 let asynch = element.has_attribute(&local_name!("async"));
708 if was_parser_inserted && !asynch {
710 self.non_blocking.set(true);
711 }
712
713 if self.prepare_the_script_text(cx).is_err() {
716 return;
717 }
718 let text = self.script_text.borrow().clone();
720 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
722 return;
723 }
724
725 if !self.upcast::<Node>().is_connected() {
727 return;
728 }
729
730 let script_type = if let Some(ty) = self.get_script_type() {
731 ty
733 } else {
734 return;
736 };
737
738 if was_parser_inserted {
742 self.parser_inserted.set(true);
743 self.non_blocking.set(false);
744 }
745
746 self.already_started.set(true);
748
749 let doc = self.owner_document();
751 self.preparation_time_document.set(Some(&doc));
752
753 if self.parser_inserted.get() && *self.parser_document != *doc {
757 return;
758 }
759
760 if !doc.scripting_enabled() {
762 return;
763 }
764
765 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
767 return;
768 }
769
770 let global = &doc.global();
771
772 if !element.has_attribute(&local_name!("src")) &&
774 global
775 .get_csp_list()
776 .should_elements_inline_type_behavior_be_blocked(
777 global,
778 element,
779 InlineCheckType::Script,
780 &text.str(),
781 self.line_number as u32,
782 )
783 {
784 warn!("Blocking inline script due to CSP");
785 return;
786 }
787
788 if script_type == ScriptType::Classic {
790 let for_attribute = element.get_attribute(&local_name!("for"));
791 let event_attribute = element.get_attribute(&local_name!("event"));
792 if let (Some(ref for_attribute), Some(ref event_attribute)) =
793 (for_attribute, event_attribute)
794 {
795 let for_value = for_attribute.value().to_ascii_lowercase();
796 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
797 if for_value != "window" {
798 return;
799 }
800
801 let event_value = event_attribute.value().to_ascii_lowercase();
802 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
803 if event_value != "onload" && event_value != "onload()" {
804 return;
805 }
806 }
807 }
808
809 let encoding = element
814 .get_attribute(&local_name!("charset"))
815 .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
816 .unwrap_or_else(|| doc.encoding());
817
818 let cors_setting = cors_setting_for_element(element);
820
821 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
823
824 let el = self.upcast::<Element>();
829 let cryptographic_nonce = if el.is_nonceable() || !el.has_attribute(&local_name!("nonce")) {
830 el.nonce_value().trim().to_owned()
831 } else {
832 String::new()
833 };
834
835 let im_attribute = element.get_attribute(&local_name!("integrity"));
838 let integrity_val = im_attribute.as_ref().map(|a| a.value());
839 let integrity_metadata = match integrity_val {
840 Some(ref value) => &***value,
841 None => "",
842 };
843
844 let referrer_policy = referrer_policy_for_element(self.upcast::<Element>());
846
847 let parser_metadata = if self.parser_inserted.get() {
852 ParserMetadata::ParserInserted
853 } else {
854 ParserMetadata::NotParserInserted
855 };
856
857 let mut options = ScriptFetchOptions {
859 cryptographic_nonce,
860 integrity_metadata: integrity_metadata.to_owned(),
861 parser_metadata,
862 referrer_policy,
863 credentials_mode: module_credentials_mode,
864 render_blocking: false,
865 };
866
867 let base_url = doc.base_url();
872
873 let kind = self.get_script_kind();
874
875 if let Some(src) = element.get_attribute(&local_name!("src")) {
876 if script_type == ScriptType::ImportMap {
880 self.queue_error_event();
883 return;
884 }
885
886 let src = src.value();
888
889 if src.is_empty() {
891 self.queue_error_event();
892 return;
893 }
894
895 self.from_an_external_file.set(true);
897
898 let url = match base_url.join(&src) {
900 Ok(url) => url,
901 Err(_) => {
902 warn!("error parsing URL for script {}", &**src);
903 self.queue_error_event();
904 return;
905 },
906 };
907
908 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
910 self.marked_as_render_blocking.set(true);
911 doc.increment_render_blocking_element_count();
912 }
913
914 self.delay_load_event(url.clone());
916
917 if self.marked_as_render_blocking.get() {
919 options.render_blocking = true;
920 }
921
922 match script_type {
924 ScriptType::Classic => {
925 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
927 },
928 ScriptType::Module => {
929 if integrity_val.is_none() {
932 options.integrity_metadata = global
933 .import_map()
934 .resolve_a_module_integrity_metadata(&url);
935 }
936
937 fetch_an_external_module_script(
939 cx,
940 url,
941 ModuleOwner::Window(Trusted::new(self)),
942 options,
943 );
944 },
945 ScriptType::ImportMap => (),
946 }
947 } else {
948 assert!(!text.is_empty());
951
952 let text_rc = Rc::new(text.clone());
953
954 match script_type {
956 ScriptType::Classic => {
957 let script = self.global().create_a_classic_script(
960 cx,
961 std::borrow::Cow::Borrowed(&text.str()),
962 base_url,
963 options,
964 ErrorReporting::Unmuted,
965 introduction_type,
966 self.line_number as u32,
967 false,
968 );
969 let result = Ok(Script::Classic(script));
970
971 if was_parser_inserted &&
972 doc.get_current_parser()
973 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
974 doc.get_script_blocking_stylesheets_count() > 0
975 {
976 doc.set_pending_parsing_blocking_script(self, Some(result));
978 } else {
979 self.execute(cx, result);
981 }
982 return;
983 },
984 ScriptType::Module => {
985 let doc = self.get_script_active_document();
987
988 self.delay_load_event(base_url.clone());
990
991 if self.potentially_render_blocking() &&
993 doc.allows_adding_render_blocking_elements()
994 {
995 self.marked_as_render_blocking.set(true);
997 doc.increment_render_blocking_element_count();
998
999 options.render_blocking = true;
1001 }
1002
1003 match kind {
1004 ExternalScriptKind::Deferred => doc.add_deferred_script(self),
1005 ExternalScriptKind::ParsingBlocking => {},
1006 ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
1007 ExternalScriptKind::Asap => doc.add_asap_script(self),
1008 }
1009
1010 fetch_inline_module_script(
1011 cx,
1012 ModuleOwner::Window(Trusted::new(self)),
1013 text_rc,
1014 base_url,
1015 options,
1016 self.line_number as u32,
1017 introduction_type,
1018 );
1019 return;
1020 },
1021 ScriptType::ImportMap => {
1022 let import_map_result = parse_an_import_map_string(
1025 ModuleOwner::Window(Trusted::new(self)),
1026 Rc::clone(&text_rc),
1027 base_url.clone(),
1028 );
1029 let script = Script::ImportMap(ScriptOrigin::internal(
1030 text_rc,
1031 base_url,
1032 options,
1033 script_type,
1034 self.global().unminified_js_dir(),
1035 import_map_result,
1036 ));
1037
1038 self.execute(cx, Ok(script));
1040 return;
1041 },
1042 }
1043 }
1044
1045 let doc = self.get_script_active_document();
1047
1048 match kind {
1050 ExternalScriptKind::Deferred => doc.add_deferred_script(self),
1051 ExternalScriptKind::ParsingBlocking => {
1052 if Some(element.get_attribute(&local_name!("src"))).is_some() &&
1053 script_type == ScriptType::Classic
1054 {
1055 doc.set_pending_parsing_blocking_script(self, None);
1056 }
1057 },
1058 ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
1059 ExternalScriptKind::Asap => doc.add_asap_script(self),
1060 }
1061 }
1062
1063 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
1065 let doc = self.owner_document();
1067
1068 if *doc != *self.preparation_time_document.get().unwrap() {
1070 return;
1071 }
1072
1073 if self.marked_as_render_blocking.replace(false) {
1075 self.marked_as_render_blocking.set(false);
1076 doc.decrement_render_blocking_element_count();
1077 }
1078
1079 let script = match result {
1080 Err(_) => {
1082 self.dispatch_event(cx, atom!("error"));
1083 return;
1084 },
1085
1086 Ok(script) => script,
1087 };
1088
1089 let neutralized_doc =
1093 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
1094 let doc = self.owner_document();
1095 doc.incr_ignore_destructive_writes_counter();
1096 Some(doc)
1097 } else {
1098 None
1099 };
1100
1101 let document = self.owner_document();
1102
1103 match script {
1104 Script::Classic(script) => {
1105 let old_script = document.GetCurrentScript();
1107
1108 if self.upcast::<Node>().is_in_a_shadow_tree() {
1111 document.set_current_script(None)
1112 } else {
1113 document.set_current_script(Some(self))
1114 }
1115
1116 _ = self.owner_window().as_global_scope().run_a_classic_script(
1118 cx,
1119 script,
1120 RethrowErrors::No,
1121 );
1122
1123 document.set_current_script(old_script.as_deref());
1125 },
1126 Script::Module(module_tree) => {
1127 document.set_current_script(None);
1129
1130 self.owner_window()
1132 .as_global_scope()
1133 .run_a_module_script(cx, module_tree, false);
1134 },
1135 Script::ImportMap(script) => {
1136 register_import_map(&self.owner_global(), script.import_map, CanGc::from_cx(cx));
1138 },
1139 }
1140
1141 if let Some(doc) = neutralized_doc {
1144 doc.decr_ignore_destructive_writes_counter();
1145 }
1146
1147 if self.from_an_external_file.get() {
1149 self.dispatch_event(cx, atom!("load"));
1150 }
1151 }
1152
1153 pub(crate) fn queue_error_event(&self) {
1154 self.owner_global()
1155 .task_manager()
1156 .dom_manipulation_task_source()
1157 .queue_simple_event(self.upcast(), atom!("error"));
1158 }
1159
1160 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1162 let element = self.upcast::<Element>();
1163
1164 let type_attr = element.get_attribute(&local_name!("type"));
1165 let language_attr = element.get_attribute(&local_name!("language"));
1166
1167 match (
1168 type_attr.as_ref().map(|t| t.value()),
1169 language_attr.as_ref().map(|l| l.value()),
1170 ) {
1171 (Some(ref ty), _) if ty.is_empty() => {
1172 debug!("script type empty, inferring js");
1173 Some(ScriptType::Classic)
1174 },
1175 (None, Some(ref lang)) if lang.is_empty() => {
1176 debug!("script type empty, inferring js");
1177 Some(ScriptType::Classic)
1178 },
1179 (None, None) => {
1180 debug!("script type empty, inferring js");
1181 Some(ScriptType::Classic)
1182 },
1183 (None, Some(ref lang)) => {
1184 debug!("script language={}", &***lang);
1185 let language = format!("text/{}", &***lang);
1186
1187 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1188 Some(ScriptType::Classic)
1189 } else {
1190 None
1191 }
1192 },
1193 (Some(ref ty), _) => {
1194 debug!("script type={}", &***ty);
1195
1196 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1197 return Some(ScriptType::Module);
1198 }
1199
1200 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1201 return Some(ScriptType::ImportMap);
1202 }
1203
1204 if SCRIPT_JS_MIMES
1205 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1206 {
1207 Some(ScriptType::Classic)
1208 } else {
1209 None
1210 }
1211 },
1212 }
1213 }
1214
1215 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1216 self.parser_inserted.set(parser_inserted);
1217 }
1218
1219 pub(crate) fn get_parser_inserted(&self) -> bool {
1220 self.parser_inserted.get()
1221 }
1222
1223 pub(crate) fn set_already_started(&self, already_started: bool) {
1224 self.already_started.set(already_started);
1225 }
1226
1227 pub(crate) fn get_non_blocking(&self) -> bool {
1228 self.non_blocking.get()
1229 }
1230
1231 fn dispatch_event(&self, cx: &mut JSContext, type_: Atom) -> bool {
1232 let window = self.owner_window();
1233 let event = Event::new(
1234 window.upcast(),
1235 type_,
1236 EventBubbles::DoesNotBubble,
1237 EventCancelable::NotCancelable,
1238 CanGc::from_cx(cx),
1239 );
1240 event.fire(self.upcast(), CanGc::from_cx(cx))
1241 }
1242
1243 fn text(&self) -> DOMString {
1244 match self.Text() {
1245 TrustedScriptOrString::String(value) => value,
1246 TrustedScriptOrString::TrustedScript(trusted_script) => {
1247 DOMString::from(trusted_script.to_string())
1248 },
1249 }
1250 }
1251}
1252
1253impl VirtualMethods for HTMLScriptElement {
1254 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1255 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1256 }
1257
1258 fn attribute_mutated(
1259 &self,
1260 cx: &mut js::context::JSContext,
1261 attr: &Attr,
1262 mutation: AttributeMutation,
1263 ) {
1264 self.super_type()
1265 .unwrap()
1266 .attribute_mutated(cx, attr, mutation);
1267 if *attr.local_name() == local_name!("src") {
1268 if let AttributeMutation::Set(..) = mutation {
1269 if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1270 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1271 }
1272 }
1273 } else if *attr.local_name() == local_name!("blocking") &&
1274 !self.has_render_blocking_attribute() &&
1275 self.marked_as_render_blocking.replace(false)
1276 {
1277 let document = self.owner_document();
1278 document.decrement_render_blocking_element_count();
1279 }
1280 }
1281
1282 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1284 if let Some(s) = self.super_type() {
1285 s.children_changed(cx, mutation);
1286 }
1287
1288 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1289 let script = DomRoot::from_ref(self);
1290 self.owner_document().add_delayed_task(
1294 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1295 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1296 }),
1297 );
1298 }
1299 }
1300
1301 fn post_connection_steps(&self, cx: &mut JSContext) {
1303 if let Some(s) = self.super_type() {
1304 s.post_connection_steps(cx);
1305 }
1306
1307 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1308 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1309 }
1310 }
1311
1312 fn cloning_steps(
1313 &self,
1314 cx: &mut JSContext,
1315 copy: &Node,
1316 maybe_doc: Option<&Document>,
1317 clone_children: CloneChildrenFlag,
1318 ) {
1319 if let Some(s) = self.super_type() {
1320 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1321 }
1322
1323 if self.already_started.get() {
1325 copy.downcast::<HTMLScriptElement>()
1326 .unwrap()
1327 .set_already_started(true);
1328 }
1329 }
1330
1331 fn unbind_from_tree(&self, _context: &UnbindContext, _can_gc: CanGc) {
1332 if self.marked_as_render_blocking.replace(false) {
1333 let document = self.owner_document();
1334 document.decrement_render_blocking_element_count();
1335 }
1336 }
1337}
1338
1339impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1340 fn Src(&self) -> TrustedScriptURLOrUSVString {
1342 let element = self.upcast::<Element>();
1343 element.get_trusted_type_url_attribute(&local_name!("src"))
1344 }
1345
1346 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1348 let element = self.upcast::<Element>();
1349 let local_name = &local_name!("src");
1350 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1351 cx,
1352 &element.owner_global(),
1353 value,
1354 &format!("HTMLScriptElement {}", local_name),
1355 )?;
1356 element.set_attribute(
1357 local_name,
1358 AttrValue::String(value.str().to_owned()),
1359 CanGc::from_cx(cx),
1360 );
1361 Ok(())
1362 }
1363
1364 make_getter!(Type, "type");
1366 make_setter!(SetType, "type");
1368
1369 make_getter!(Charset, "charset");
1371 make_setter!(SetCharset, "charset");
1373
1374 fn Async(&self) -> bool {
1376 self.non_blocking.get() ||
1377 self.upcast::<Element>()
1378 .has_attribute(&local_name!("async"))
1379 }
1380
1381 fn SetAsync(&self, value: bool, can_gc: CanGc) {
1383 self.non_blocking.set(false);
1384 self.upcast::<Element>()
1385 .set_bool_attribute(&local_name!("async"), value, can_gc);
1386 }
1387
1388 make_bool_getter!(Defer, "defer");
1390 make_bool_setter!(SetDefer, "defer");
1392
1393 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1395 self.blocking.or_init(|| {
1396 DOMTokenList::new(
1397 self.upcast(),
1398 &local_name!("blocking"),
1399 Some(vec![Atom::from("render")]),
1400 CanGc::from_cx(cx),
1401 )
1402 })
1403 }
1404
1405 make_bool_getter!(NoModule, "nomodule");
1407 make_bool_setter!(SetNoModule, "nomodule");
1409
1410 make_getter!(Integrity, "integrity");
1412 make_setter!(SetIntegrity, "integrity");
1414
1415 make_getter!(Event, "event");
1417 make_setter!(SetEvent, "event");
1419
1420 make_getter!(HtmlFor, "for");
1422 make_setter!(SetHtmlFor, "for");
1424
1425 fn GetCrossOrigin(&self) -> Option<DOMString> {
1427 reflect_cross_origin_attribute(self.upcast::<Element>())
1428 }
1429
1430 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1432 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1433 }
1434
1435 fn ReferrerPolicy(&self) -> DOMString {
1437 reflect_referrer_policy_attribute(self.upcast::<Element>())
1438 }
1439
1440 make_setter!(SetReferrerPolicy, "referrerpolicy");
1442
1443 fn InnerText(&self) -> TrustedScriptOrString {
1445 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1447 }
1448
1449 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1451 let value = TrustedScript::get_trusted_type_compliant_string(
1454 cx,
1455 &self.owner_global(),
1456 input,
1457 "HTMLScriptElement innerText",
1458 )?;
1459 *self.script_text.borrow_mut() = value.clone();
1460 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1462 Ok(())
1463 }
1464
1465 fn Text(&self) -> TrustedScriptOrString {
1467 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1468 }
1469
1470 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1472 let value = TrustedScript::get_trusted_type_compliant_string(
1475 cx,
1476 &self.owner_global(),
1477 value,
1478 "HTMLScriptElement text",
1479 )?;
1480 *self.script_text.borrow_mut() = value.clone();
1482 Node::string_replace_all(cx, value, self.upcast::<Node>());
1484 Ok(())
1485 }
1486
1487 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1489 Some(TrustedScriptOrString::String(
1491 self.upcast::<Node>().GetTextContent()?,
1492 ))
1493 }
1494
1495 fn SetTextContent(
1497 &self,
1498 cx: &mut JSContext,
1499 value: Option<TrustedScriptOrString>,
1500 ) -> Fallible<()> {
1501 let value = TrustedScript::get_trusted_type_compliant_string(
1504 cx,
1505 &self.owner_global(),
1506 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1507 "HTMLScriptElement textContent",
1508 )?;
1509 *self.script_text.borrow_mut() = value.clone();
1511 self.upcast::<Node>()
1513 .set_text_content_for_element(cx, Some(value));
1514 Ok(())
1515 }
1516
1517 fn Supports(_window: &Window, type_: DOMString) -> bool {
1519 matches!(&*type_.str(), "classic" | "module" | "importmap")
1522 }
1523}
1524
1525pub(crate) fn substitute_with_local_script(
1526 window: &Window,
1527 script: &mut Cow<'_, str>,
1528 url: ServoUrl,
1529) {
1530 if window.local_script_source().is_none() {
1531 return;
1532 }
1533 let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1534 path = path.join(&url[url::Position::BeforeHost..]);
1535 debug!("Attempting to read script stored at: {:?}", path);
1536 match read_to_string(path.clone()) {
1537 Ok(local_script) => {
1538 debug!("Found script stored at: {:?}", path);
1539 *script = Cow::Owned(local_script);
1540 },
1541 Err(why) => warn!("Could not restore script from file {:?}", why),
1542 }
1543}
1544
1545#[derive(Clone, Copy)]
1546pub(crate) enum ExternalScriptKind {
1547 Deferred,
1548 ParsingBlocking,
1549 AsapInOrder,
1550 Asap,
1551}