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;
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 script_bindings::cell::DomRefCell;
23use servo_base::id::WebViewId;
24use servo_url::ServoUrl;
25use style::attr::AttrValue;
26use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
27use stylo_atoms::Atom;
28
29use crate::document_loader::{LoadBlocker, LoadType};
30use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
31use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
32use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
33use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
34use crate::dom::bindings::codegen::UnionTypes::{
35 TrustedScriptOrString, TrustedScriptURLOrUSVString,
36};
37use crate::dom::bindings::error::Fallible;
38use crate::dom::bindings::inheritance::Castable;
39use crate::dom::bindings::refcounted::Trusted;
40use crate::dom::bindings::reflector::DomGlobal;
41use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
42use crate::dom::bindings::str::DOMString;
43use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
44use crate::dom::document::Document;
45use crate::dom::domtokenlist::DOMTokenList;
46use crate::dom::element::attributes::storage::AttrRef;
47use crate::dom::element::{
48 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
49 cors_settings_attribute_credential_mode, referrer_policy_for_element,
50 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
51};
52use crate::dom::event::eventtarget::EventTarget;
53use crate::dom::globalscope::GlobalScope;
54use crate::dom::globalscope::script_execution::{ClassicScript, ErrorReporting, RethrowErrors};
55use crate::dom::html::htmlelement::HTMLElement;
56use crate::dom::node::virtualmethods::VirtualMethods;
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::window::Window;
62use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
63use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
64use crate::script_module::{
65 ImportMap, ModuleTree, ScriptFetchOptions, fetch_an_external_module_script,
66 fetch_inline_module_script, parse_an_import_map_string, register_import_map,
67};
68use crate::script_runtime::IntroductionType;
69
70#[dom_struct]
71pub(crate) struct HTMLScriptElement {
72 htmlelement: HTMLElement,
73
74 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
76
77 already_started: Cell<bool>,
79
80 parser_inserted: Cell<bool>,
82
83 non_blocking: Cell<bool>,
87
88 parser_document: Dom<Document>,
91
92 preparation_time_document: MutNullableDom<Document>,
95
96 line_number: u64,
98
99 script_text: DomRefCell<DOMString>,
101
102 from_an_external_file: Cell<bool>,
104
105 blocking: MutNullableDom<DOMTokenList>,
107
108 marked_as_render_blocking: Cell<bool>,
111
112 result: DomRefCell<Option<ScriptResult>>,
114}
115
116impl HTMLScriptElement {
117 fn new_inherited(
118 local_name: LocalName,
119 prefix: Option<Prefix>,
120 document: &Document,
121 creator: ElementCreator,
122 ) -> HTMLScriptElement {
123 HTMLScriptElement {
124 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
125 already_started: Cell::new(false),
126 delaying_the_load_event: Default::default(),
127 parser_inserted: Cell::new(creator.is_parser_created()),
128 non_blocking: Cell::new(!creator.is_parser_created()),
129 parser_document: Dom::from_ref(document),
130 preparation_time_document: MutNullableDom::new(None),
131 line_number: creator.return_line_number(),
132 script_text: DomRefCell::new(DOMString::new()),
133 from_an_external_file: Cell::new(false),
134 blocking: Default::default(),
135 marked_as_render_blocking: Default::default(),
136 result: DomRefCell::new(None),
137 }
138 }
139
140 pub(crate) fn new(
141 cx: &mut js::context::JSContext,
142 local_name: LocalName,
143 prefix: Option<Prefix>,
144 document: &Document,
145 proto: Option<HandleObject>,
146 creator: ElementCreator,
147 ) -> DomRoot<HTMLScriptElement> {
148 Node::reflect_node_with_proto(
149 cx,
150 Box::new(HTMLScriptElement::new_inherited(
151 local_name, prefix, document, creator,
152 )),
153 document,
154 proto,
155 )
156 }
157
158 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
163 debug_assert!(self.delaying_the_load_event.borrow().is_none());
164
165 *self.delaying_the_load_event.borrow_mut() =
166 Some(LoadBlocker::new(document, LoadType::Script(url)));
167 }
168
169 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
176 let element = self.upcast::<Element>();
177
178 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
179 ExternalScriptKind::Asap
180 } else if !self.parser_inserted.get() {
181 ExternalScriptKind::AsapInOrder
182 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
183 {
184 ExternalScriptKind::Deferred
185 } else {
186 ExternalScriptKind::ParsingBlocking
187 }
188 }
189
190 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
192 match script_kind {
193 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
194 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
195 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
196 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
197 }
198 }
199}
200
201pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
204 "application/ecmascript",
205 "application/javascript",
206 "application/x-ecmascript",
207 "application/x-javascript",
208 "text/ecmascript",
209 "text/javascript",
210 "text/javascript1.0",
211 "text/javascript1.1",
212 "text/javascript1.2",
213 "text/javascript1.3",
214 "text/javascript1.4",
215 "text/javascript1.5",
216 "text/jscript",
217 "text/livescript",
218 "text/x-ecmascript",
219 "text/x-javascript",
220];
221
222#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
223pub(crate) enum ScriptType {
224 Classic,
225 Module,
226 ImportMap,
227}
228
229fn finish_fetching_a_script(
231 elem: &HTMLScriptElement,
232 script_kind: ExternalScriptKind,
233 cx: &mut JSContext,
234) {
235 let load = elem.result.take().expect("Result must be ready to proceed");
236
237 match script_kind {
239 ExternalScriptKind::Asap => {
240 let document = elem.preparation_time_document.get().unwrap();
241 document.asap_script_loaded(cx, elem, load)
242 },
243 ExternalScriptKind::AsapInOrder => {
244 let document = elem.preparation_time_document.get().unwrap();
245 document.asap_in_order_script_loaded(cx, elem, load)
246 },
247 ExternalScriptKind::Deferred => {
248 let document = elem.parser_document.as_rooted();
249 document.deferred_script_loaded(cx, elem, load);
250 },
251 ExternalScriptKind::ParsingBlocking => {
252 let document = elem.parser_document.as_rooted();
253 document.pending_parsing_blocking_script_loaded(elem, load, cx);
254 },
255 }
256
257 LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
259}
260
261pub(crate) type ScriptResult = Result<Script, ()>;
262
263#[derive(JSTraceable, MallocSizeOf)]
265pub(crate) enum Script {
266 Classic(ClassicScript),
267 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
268 ImportMap(Fallible<ImportMap>),
269}
270
271struct ClassicContext {
273 elem: Trusted<HTMLScriptElement>,
275 kind: ExternalScriptKind,
277 character_encoding: &'static Encoding,
280 data: Vec<u8>,
282 metadata: Option<Metadata>,
284 url: ServoUrl,
286 status: Result<(), NetworkError>,
288 fetch_options: ScriptFetchOptions,
290 response_was_cors_cross_origin: bool,
292}
293
294impl FetchResponseListener for ClassicContext {
295 fn process_request_body(&mut self, _: RequestId) {}
297
298 fn process_response(
299 &mut self,
300 _: &mut js::context::JSContext,
301 _: RequestId,
302 metadata: Result<FetchMetadata, NetworkError>,
303 ) {
304 self.metadata = metadata.ok().map(|meta| {
305 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
306 match meta {
307 FetchMetadata::Unfiltered(m) => m,
308 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
309 }
310 });
311
312 let status = self
313 .metadata
314 .as_ref()
315 .map(|m| m.status.clone())
316 .unwrap_or_else(HttpStatus::new_error);
317
318 self.status = {
319 if status.is_error() {
320 Err(NetworkError::ResourceLoadError(
321 "No http status code received".to_owned(),
322 ))
323 } else if status.is_success() {
324 Ok(())
325 } else {
326 Err(NetworkError::ResourceLoadError(format!(
327 "HTTP error code {}",
328 status.code()
329 )))
330 }
331 };
332 }
333
334 fn process_response_chunk(
335 &mut self,
336 _: &mut js::context::JSContext,
337 _: RequestId,
338 mut chunk: Vec<u8>,
339 ) {
340 if self.status.is_ok() {
341 self.data.append(&mut chunk);
342 }
343 }
344
345 fn process_response_eof(
348 mut self,
349 cx: &mut js::context::JSContext,
350 _: RequestId,
351 response: Result<(), NetworkError>,
352 timing: ResourceFetchTiming,
353 ) {
354 network_listener::submit_timing(cx, &self, &response, &timing);
356
357 let elem = self.elem.root();
358
359 match (response.as_ref(), self.status.as_ref()) {
360 (Err(error), _) | (_, Err(error)) => {
361 error!("Fetching classic script failed {:?} ({})", error, self.url);
362 *elem.result.borrow_mut() = Some(Err(()));
364 finish_fetching_a_script(&elem, self.kind, cx);
365 return;
366 },
367 _ => {},
368 };
369
370 let metadata = self.metadata.take().unwrap();
371 let final_url = metadata.final_url;
372
373 let encoding = metadata
376 .charset
377 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
378 .unwrap_or(self.character_encoding);
379
380 let (mut source_text, _, _) = encoding.decode(&self.data);
382
383 let global = elem.global();
384
385 if let Some(window) = global.downcast::<Window>() &&
386 let Some(script_source) = window.local_script_source()
387 {
388 substitute_with_local_script(script_source, &mut source_text, final_url.clone());
389 }
390
391 let muted_errors = self.response_was_cors_cross_origin;
393
394 let script = global.create_a_classic_script(
397 cx,
398 source_text,
399 final_url,
400 self.fetch_options.clone(),
401 ErrorReporting::from(muted_errors),
402 Some(IntroductionType::SRC_SCRIPT),
403 1,
404 true,
405 );
406
407 *elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
438 finish_fetching_a_script(&elem, self.kind, cx);
439 }
441
442 fn process_csp_violations(
443 &mut self,
444 cx: &mut js::context::JSContext,
445 _request_id: RequestId,
446 violations: Vec<Violation>,
447 ) {
448 let global = &self.resource_timing_global();
449 let elem = self.elem.root();
450 global.report_csp_violations(cx, violations, Some(elem.upcast()), None);
451 }
452}
453
454impl ResourceTimingListener for ClassicContext {
455 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
456 let initiator_type = InitiatorType::LocalName(
457 self.elem
458 .root()
459 .upcast::<Element>()
460 .local_name()
461 .to_string(),
462 );
463 (initiator_type, self.url.clone())
464 }
465
466 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
467 self.elem.root().owner_document().global()
468 }
469}
470
471#[allow(clippy::too_many_arguments)]
474pub(crate) fn script_fetch_request(
475 webview_id: WebViewId,
476 url: ServoUrl,
477 cors_setting: Option<CorsSettings>,
478 options: ScriptFetchOptions,
479 referrer: Referrer,
480) -> RequestBuilder {
481 create_a_potential_cors_request(
484 Some(webview_id),
485 url,
486 Destination::Script,
487 cors_setting,
488 None,
489 referrer,
490 )
491 .parser_metadata(options.parser_metadata)
492 .integrity_metadata(options.integrity_metadata.clone())
493 .referrer_policy(options.referrer_policy)
494 .cryptographic_nonce_metadata(options.cryptographic_nonce)
495}
496
497fn fetch_a_classic_script(
499 script: &HTMLScriptElement,
500 kind: ExternalScriptKind,
501 url: ServoUrl,
502 cors_setting: Option<CorsSettings>,
503 options: ScriptFetchOptions,
504 character_encoding: &'static Encoding,
505) {
506 let doc = script.owner_document();
508 let global = script.global();
509 let referrer = global.get_referrer();
510 let request = script_fetch_request(
511 doc.webview_id(),
512 url.clone(),
513 cors_setting,
514 options.clone(),
515 referrer,
516 )
517 .with_global_scope(&global);
518
519 let context = ClassicContext {
522 elem: Trusted::new(script),
523 kind,
524 character_encoding,
525 data: vec![],
526 metadata: None,
527 url,
528 status: Ok(()),
529 fetch_options: options,
530 response_was_cors_cross_origin: false,
531 };
532 doc.fetch_background(request, context);
533}
534
535impl HTMLScriptElement {
536 pub(crate) fn set_initial_script_text(&self) {
538 *self.script_text.borrow_mut() = self.text();
539 }
540
541 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
543 if self.script_text.borrow().clone() != self.text() {
547 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
548 cx,
549 &self.owner_global(),
550 self.Text(),
551 "HTMLScriptElement text",
552 )?;
553 }
554
555 Ok(())
556 }
557
558 fn has_render_blocking_attribute(&self) -> bool {
559 self.blocking
560 .get()
561 .is_some_and(|list| list.Contains("render".into()))
562 }
563
564 fn potentially_render_blocking(&self) -> bool {
566 if self.has_render_blocking_attribute() {
570 return true;
571 }
572 let element = self.upcast::<Element>();
573 self.get_script_type()
577 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
578 self.parser_inserted.get() &&
579 !element.has_attribute(&local_name!("async")) &&
580 !element.has_attribute(&local_name!("defer"))
581 }
582
583 pub(crate) fn prepare(
585 &self,
586 cx: &mut JSContext,
587 introduction_type_override: Option<&'static CStr>,
588 ) {
589 let introduction_type =
590 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
591
592 if self.already_started.get() {
594 return;
595 }
596
597 let was_parser_inserted = self.parser_inserted.get();
602 self.parser_inserted.set(false);
603
604 let element = self.upcast::<Element>();
607 let asynch = element.has_attribute(&local_name!("async"));
608 if was_parser_inserted && !asynch {
610 self.non_blocking.set(true);
611 }
612
613 if self.prepare_the_script_text(cx).is_err() {
616 return;
617 }
618 let text = self.script_text.borrow().clone();
620 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
622 return;
623 }
624
625 if !self.upcast::<Node>().is_connected() {
627 return;
628 }
629
630 let script_type = if let Some(ty) = self.get_script_type() {
631 ty
633 } else {
634 return;
636 };
637
638 if was_parser_inserted {
642 self.parser_inserted.set(true);
643 self.non_blocking.set(false);
644 }
645
646 self.already_started.set(true);
648
649 let doc = self.owner_document();
651 self.preparation_time_document.set(Some(&doc));
652
653 if self.parser_inserted.get() && *self.parser_document != *doc {
657 return;
658 }
659
660 if !doc.scripting_enabled() {
662 return;
663 }
664
665 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
667 return;
668 }
669
670 let global = &doc.global();
671
672 if !element.has_attribute(&local_name!("src")) &&
674 global
675 .get_csp_list()
676 .should_elements_inline_type_behavior_be_blocked(
677 cx,
678 global,
679 element,
680 InlineCheckType::Script,
681 &text.str(),
682 self.line_number as u32,
683 )
684 {
685 warn!("Blocking inline script due to CSP");
686 return;
687 }
688
689 if script_type == ScriptType::Classic {
691 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
692 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
693 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
694 let for_value = for_attribute.to_ascii_lowercase();
695 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
696 if for_value != "window" {
697 return;
698 }
699
700 let event_value = event_attribute.to_ascii_lowercase();
701 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
702 if event_value != "onload" && event_value != "onload()" {
703 return;
704 }
705 }
706 }
707
708 let encoding = element
713 .get_attribute_string_value(&local_name!("charset"))
714 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
715 .unwrap_or_else(|| doc.encoding());
716
717 let cors_setting = cors_setting_for_element(element);
719
720 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
722
723 let cryptographic_nonce =
728 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
729 element.nonce_value().trim().to_owned()
730 } else {
731 String::new()
732 };
733
734 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
737 let integrity_val_is_none = integrity_val.is_none();
738 let integrity_metadata = integrity_val.unwrap_or_default();
739
740 let referrer_policy = referrer_policy_for_element(element);
742
743 let parser_metadata = if self.parser_inserted.get() {
748 ParserMetadata::ParserInserted
749 } else {
750 ParserMetadata::NotParserInserted
751 };
752
753 let mut options = ScriptFetchOptions {
755 cryptographic_nonce,
756 integrity_metadata,
757 parser_metadata,
758 referrer_policy,
759 credentials_mode: module_credentials_mode,
760 render_blocking: false,
761 };
762
763 let base_url = doc.base_url();
766
767 let kind = self.get_script_kind(script_type);
768 let delayed_document = self.get_script_active_document(kind);
769
770 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
773 if script_type == ScriptType::ImportMap {
775 self.queue_error_event();
778 return;
779 }
780
781 if src.is_empty() {
783 self.queue_error_event();
784 return;
785 }
786
787 self.from_an_external_file.set(true);
789
790 let url = match base_url.join(&src) {
792 Ok(url) => url,
793 Err(_) => {
794 warn!("error parsing URL for script {}", src);
795 self.queue_error_event();
796 return;
797 },
798 };
799
800 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
802 self.marked_as_render_blocking.set(true);
803 doc.increment_render_blocking_element_count();
804 }
805
806 self.delay_load_event(&delayed_document, url.clone());
808
809 if self.marked_as_render_blocking.get() {
811 options.render_blocking = true;
812 }
813
814 match script_type {
816 ScriptType::Classic => {
817 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
819 },
820 ScriptType::Module => {
821 if integrity_val_is_none {
824 options.integrity_metadata = global
825 .import_map()
826 .resolve_a_module_integrity_metadata(&url);
827 }
828
829 let script = DomRoot::from_ref(self);
830
831 fetch_an_external_module_script(
833 cx,
834 url,
835 global,
836 options,
837 move |cx, module_tree| {
838 let load = module_tree.map(Script::Module).ok_or(());
839 *script.result.borrow_mut() = Some(load);
840
841 finish_fetching_a_script(&script, kind, cx);
842 },
843 );
844 },
845 ScriptType::ImportMap => (),
846 }
847 } else {
848 assert!(!text.is_empty());
851
852 let text_rc = Rc::new(text.clone());
853
854 match script_type {
856 ScriptType::Classic => {
857 let script = self.global().create_a_classic_script(
860 cx,
861 std::borrow::Cow::Borrowed(&text.str()),
862 base_url,
863 options,
864 ErrorReporting::Unmuted,
865 introduction_type,
866 self.line_number as u32,
867 false,
868 );
869 let result = Ok(Script::Classic(script));
870
871 if was_parser_inserted &&
872 doc.get_current_parser()
873 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
874 doc.has_a_stylesheet_that_is_blocking_scripts()
875 {
876 doc.set_pending_parsing_blocking_script(self, Some(result));
878 } else {
879 self.execute(cx, result);
881 }
882 return;
883 },
884 ScriptType::Module => {
885 self.delay_load_event(&delayed_document, base_url.clone());
887
888 if self.potentially_render_blocking() &&
890 doc.allows_adding_render_blocking_elements()
891 {
892 self.marked_as_render_blocking.set(true);
894 doc.increment_render_blocking_element_count();
895
896 options.render_blocking = true;
898 }
899
900 let script = DomRoot::from_ref(self);
901 fetch_inline_module_script(
904 cx,
905 global,
906 text_rc,
907 base_url,
908 options,
909 self.line_number as u32,
910 introduction_type,
911 move |_, module_tree| {
912 let load = module_tree.map(Script::Module).ok_or(());
913 *script.result.borrow_mut() = Some(load);
914
915 let trusted = Trusted::new(&*script);
916
917 script
919 .owner_global()
920 .task_manager()
921 .networking_task_source()
922 .queue(task!(terminate_module_fetch: move |cx| {
923 finish_fetching_a_script(&trusted.root(), kind, cx);
925 }));
926 },
927 );
928 },
929 ScriptType::ImportMap => {
930 let import_map_result =
933 parse_an_import_map_string(cx, global, Rc::clone(&text_rc), base_url);
934 let script = Script::ImportMap(import_map_result);
935
936 self.execute(cx, Ok(script));
938 return;
939 },
940 }
941 }
942
943 match kind {
945 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
946 ExternalScriptKind::ParsingBlocking => {
947 delayed_document.set_pending_parsing_blocking_script(self, None);
948 },
949 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
950 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
951 }
952 }
953
954 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
956 let doc = self.owner_document();
958
959 if *doc != *self.preparation_time_document.get().unwrap() {
961 return;
962 }
963
964 if self.marked_as_render_blocking.replace(false) {
966 self.marked_as_render_blocking.set(false);
967 doc.decrement_render_blocking_element_count();
968 }
969
970 let script = match result {
971 Err(_) => {
973 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
974 return;
975 },
976
977 Ok(script) => script,
978 };
979
980 let neutralized_doc =
984 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
985 let doc = self.owner_document();
986 doc.incr_ignore_destructive_writes_counter();
987 Some(doc)
988 } else {
989 None
990 };
991
992 let document = self.owner_document();
993
994 match script {
995 Script::Classic(script) => {
996 let old_script = document.GetCurrentScript();
998
999 if self.upcast::<Node>().is_in_a_shadow_tree() {
1002 document.set_current_script(None)
1003 } else {
1004 document.set_current_script(Some(self))
1005 }
1006
1007 _ = self
1009 .owner_global()
1010 .run_a_classic_script(cx, script, RethrowErrors::No);
1011
1012 document.set_current_script(old_script.as_deref());
1014 },
1015 Script::Module(module_tree) => {
1016 document.set_current_script(None);
1018
1019 self.owner_global()
1021 .run_a_module_script(cx, module_tree, false);
1022 },
1023 Script::ImportMap(import_map) => {
1024 register_import_map(cx, &self.owner_global(), import_map);
1026 },
1027 }
1028
1029 if let Some(doc) = neutralized_doc {
1032 doc.decr_ignore_destructive_writes_counter();
1033 }
1034
1035 if self.from_an_external_file.get() {
1037 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1038 }
1039 }
1040
1041 pub(crate) fn queue_error_event(&self) {
1042 self.owner_global()
1043 .task_manager()
1044 .dom_manipulation_task_source()
1045 .queue_simple_event(self.upcast(), atom!("error"));
1046 }
1047
1048 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1050 let element = self.upcast::<Element>();
1051
1052 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1053 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1054
1055 match (type_attr, language_attr) {
1056 (Some(ty), _) if ty.is_empty() => {
1057 debug!("script type empty, inferring js");
1058 Some(ScriptType::Classic)
1059 },
1060 (None, Some(lang)) if lang.is_empty() => {
1061 debug!("script type empty, inferring js");
1062 Some(ScriptType::Classic)
1063 },
1064 (None, None) => {
1065 debug!("script type empty, inferring js");
1066 Some(ScriptType::Classic)
1067 },
1068 (None, Some(lang)) => {
1069 debug!("script language={}", lang);
1070 let language = format!("text/{}", lang);
1071
1072 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1073 Some(ScriptType::Classic)
1074 } else {
1075 None
1076 }
1077 },
1078 (Some(ty), _) => {
1079 debug!("script type={}", ty);
1080
1081 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1082 return Some(ScriptType::Module);
1083 }
1084
1085 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1086 return Some(ScriptType::ImportMap);
1087 }
1088
1089 if SCRIPT_JS_MIMES
1090 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1091 {
1092 Some(ScriptType::Classic)
1093 } else {
1094 None
1095 }
1096 },
1097 }
1098 }
1099
1100 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1101 self.parser_inserted.set(parser_inserted);
1102 }
1103
1104 pub(crate) fn set_already_started(&self, already_started: bool) {
1105 self.already_started.set(already_started);
1106 }
1107
1108 fn text(&self) -> DOMString {
1109 match self.Text() {
1110 TrustedScriptOrString::String(value) => value,
1111 TrustedScriptOrString::TrustedScript(trusted_script) => {
1112 DOMString::from(trusted_script.to_string())
1113 },
1114 }
1115 }
1116}
1117
1118impl VirtualMethods for HTMLScriptElement {
1119 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1120 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1121 }
1122
1123 fn attribute_mutated(
1124 &self,
1125 cx: &mut js::context::JSContext,
1126 attr: AttrRef<'_>,
1127 mutation: AttributeMutation,
1128 ) {
1129 self.super_type()
1130 .unwrap()
1131 .attribute_mutated(cx, attr, mutation);
1132 if *attr.local_name() == local_name!("src") {
1133 if let AttributeMutation::Set(..) = mutation &&
1134 !self.parser_inserted.get() &&
1135 self.upcast::<Node>().is_connected()
1136 {
1137 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1138 }
1139 } else if *attr.local_name() == local_name!("blocking") &&
1140 !self.has_render_blocking_attribute() &&
1141 self.marked_as_render_blocking.replace(false)
1142 {
1143 let document = self.owner_document();
1144 document.decrement_render_blocking_element_count();
1145 }
1146 }
1147
1148 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1150 if let Some(s) = self.super_type() {
1151 s.children_changed(cx, mutation);
1152 }
1153
1154 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1155 let script = DomRoot::from_ref(self);
1156 self.owner_document().add_delayed_task(
1160 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1161 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1162 }),
1163 );
1164 }
1165 }
1166
1167 fn post_connection_steps(&self, cx: &mut JSContext) {
1169 if let Some(s) = self.super_type() {
1170 s.post_connection_steps(cx);
1171 }
1172
1173 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1174 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1175 }
1176 }
1177
1178 fn cloning_steps(
1179 &self,
1180 cx: &mut JSContext,
1181 copy: &Node,
1182 maybe_doc: Option<&Document>,
1183 clone_children: CloneChildrenFlag,
1184 ) {
1185 if let Some(s) = self.super_type() {
1186 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1187 }
1188
1189 if self.already_started.get() {
1191 copy.downcast::<HTMLScriptElement>()
1192 .unwrap()
1193 .set_already_started(true);
1194 }
1195 }
1196
1197 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1198 self.super_type().unwrap().unbind_from_tree(cx, context);
1199
1200 if self.marked_as_render_blocking.replace(false) {
1201 let document = self.owner_document();
1202 document.decrement_render_blocking_element_count();
1203 }
1204 }
1205}
1206
1207impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1208 fn Src(&self) -> TrustedScriptURLOrUSVString {
1210 let element = self.upcast::<Element>();
1211 element.get_trusted_type_url_attribute(&local_name!("src"))
1212 }
1213
1214 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1216 let element = self.upcast::<Element>();
1217 let local_name = &local_name!("src");
1218 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1219 cx,
1220 &element.owner_global(),
1221 value,
1222 &format!("HTMLScriptElement {}", local_name),
1223 )?;
1224 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1225 Ok(())
1226 }
1227
1228 make_getter!(Type, "type");
1230 make_setter!(SetType, "type");
1232
1233 make_getter!(Charset, "charset");
1235 make_setter!(SetCharset, "charset");
1237
1238 fn Async(&self) -> bool {
1240 self.non_blocking.get() ||
1241 self.upcast::<Element>()
1242 .has_attribute(&local_name!("async"))
1243 }
1244
1245 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1247 self.non_blocking.set(false);
1248 self.upcast::<Element>()
1249 .set_bool_attribute(cx, &local_name!("async"), value);
1250 }
1251
1252 make_bool_getter!(Defer, "defer");
1254 make_bool_setter!(SetDefer, "defer");
1256
1257 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1259 self.blocking.or_init(|| {
1260 DOMTokenList::new(
1261 cx,
1262 self.upcast(),
1263 &local_name!("blocking"),
1264 Some(vec![Atom::from("render")]),
1265 )
1266 })
1267 }
1268
1269 make_bool_getter!(NoModule, "nomodule");
1271 make_bool_setter!(SetNoModule, "nomodule");
1273
1274 make_getter!(Integrity, "integrity");
1276 make_setter!(SetIntegrity, "integrity");
1278
1279 make_getter!(Event, "event");
1281 make_setter!(SetEvent, "event");
1283
1284 make_getter!(HtmlFor, "for");
1286 make_setter!(SetHtmlFor, "for");
1288
1289 fn GetCrossOrigin(&self) -> Option<DOMString> {
1291 reflect_cross_origin_attribute(self.upcast::<Element>())
1292 }
1293
1294 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1296 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1297 }
1298
1299 fn ReferrerPolicy(&self) -> DOMString {
1301 reflect_referrer_policy_attribute(self.upcast::<Element>())
1302 }
1303
1304 make_setter!(SetReferrerPolicy, "referrerpolicy");
1306
1307 fn InnerText(&self) -> TrustedScriptOrString {
1309 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1311 }
1312
1313 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1315 let value = TrustedScript::get_trusted_type_compliant_string(
1318 cx,
1319 &self.owner_global(),
1320 input,
1321 "HTMLScriptElement innerText",
1322 )?;
1323 *self.script_text.borrow_mut() = value.clone();
1324 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1326 Ok(())
1327 }
1328
1329 fn Text(&self) -> TrustedScriptOrString {
1331 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1332 }
1333
1334 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1336 let value = TrustedScript::get_trusted_type_compliant_string(
1339 cx,
1340 &self.owner_global(),
1341 value,
1342 "HTMLScriptElement text",
1343 )?;
1344 *self.script_text.borrow_mut() = value.clone();
1346 Node::string_replace_all(cx, value, self.upcast::<Node>());
1348 Ok(())
1349 }
1350
1351 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1353 Some(TrustedScriptOrString::String(
1355 self.upcast::<Node>().GetTextContent()?,
1356 ))
1357 }
1358
1359 fn SetTextContent(
1361 &self,
1362 cx: &mut JSContext,
1363 value: Option<TrustedScriptOrString>,
1364 ) -> Fallible<()> {
1365 let value = TrustedScript::get_trusted_type_compliant_string(
1368 cx,
1369 &self.owner_global(),
1370 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1371 "HTMLScriptElement textContent",
1372 )?;
1373 *self.script_text.borrow_mut() = value.clone();
1375 self.upcast::<Node>()
1377 .set_text_content_for_element(cx, Some(value));
1378 Ok(())
1379 }
1380
1381 fn Supports(_window: &Window, type_: DOMString) -> bool {
1383 matches!(&*type_.str(), "classic" | "module" | "importmap")
1386 }
1387}
1388
1389pub fn substitute_with_local_script(script_source: &str, script: &mut Cow<'_, str>, url: ServoUrl) {
1390 let mut path = PathBuf::from(script_source);
1391 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1392 debug!("Attempting to read script stored at: {:?}", path);
1393 match read_to_string(path.clone()) {
1394 Ok(local_script) => {
1395 debug!("Found script stored at: {:?}", path);
1396 *script = Cow::Owned(local_script);
1397 },
1398 Err(why) => warn!("Could not restore script from file {:?}", why),
1399 }
1400}
1401
1402#[derive(Clone, Copy)]
1403enum ExternalScriptKind {
1404 Deferred,
1405 ParsingBlocking,
1406 AsapInOrder,
1407 Asap,
1408}