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 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
454 self.data.reserve(size - self.data.len());
455 }
456}
457
458impl ResourceTimingListener for ClassicContext {
459 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
460 let initiator_type = InitiatorType::LocalName(
461 self.elem
462 .root()
463 .upcast::<Element>()
464 .local_name()
465 .to_string(),
466 );
467 (initiator_type, self.url.clone())
468 }
469
470 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
471 self.elem.root().owner_document().global()
472 }
473}
474
475#[allow(clippy::too_many_arguments)]
478pub(crate) fn script_fetch_request(
479 webview_id: WebViewId,
480 url: ServoUrl,
481 cors_setting: Option<CorsSettings>,
482 options: ScriptFetchOptions,
483 referrer: Referrer,
484) -> RequestBuilder {
485 create_a_potential_cors_request(
488 Some(webview_id),
489 url,
490 Destination::Script,
491 cors_setting,
492 None,
493 referrer,
494 )
495 .parser_metadata(options.parser_metadata)
496 .integrity_metadata(options.integrity_metadata.clone())
497 .referrer_policy(options.referrer_policy)
498 .cryptographic_nonce_metadata(options.cryptographic_nonce)
499}
500
501fn fetch_a_classic_script(
503 script: &HTMLScriptElement,
504 kind: ExternalScriptKind,
505 url: ServoUrl,
506 cors_setting: Option<CorsSettings>,
507 options: ScriptFetchOptions,
508 character_encoding: &'static Encoding,
509) {
510 let doc = script.owner_document();
512 let global = script.global();
513 let referrer = global.get_referrer();
514 let request = script_fetch_request(
515 doc.webview_id(),
516 url.clone(),
517 cors_setting,
518 options.clone(),
519 referrer,
520 )
521 .with_global_scope(&global);
522
523 let context = ClassicContext {
526 elem: Trusted::new(script),
527 kind,
528 character_encoding,
529 data: vec![],
530 metadata: None,
531 url,
532 status: Ok(()),
533 fetch_options: options,
534 response_was_cors_cross_origin: false,
535 };
536 doc.fetch_background(request, context);
537}
538
539impl HTMLScriptElement {
540 pub(crate) fn set_initial_script_text(&self) {
542 *self.script_text.borrow_mut() = self.text();
543 }
544
545 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
547 if self.script_text.borrow().clone() != self.text() {
551 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
552 cx,
553 &self.owner_global(),
554 self.Text(),
555 "HTMLScriptElement text",
556 )?;
557 }
558
559 Ok(())
560 }
561
562 fn has_render_blocking_attribute(&self) -> bool {
563 self.blocking
564 .get()
565 .is_some_and(|list| list.Contains("render".into()))
566 }
567
568 fn potentially_render_blocking(&self) -> bool {
570 if self.has_render_blocking_attribute() {
574 return true;
575 }
576 let element = self.upcast::<Element>();
577 self.get_script_type()
581 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
582 self.parser_inserted.get() &&
583 !element.has_attribute(&local_name!("async")) &&
584 !element.has_attribute(&local_name!("defer"))
585 }
586
587 pub(crate) fn prepare(
589 &self,
590 cx: &mut JSContext,
591 introduction_type_override: Option<&'static CStr>,
592 ) {
593 let introduction_type =
594 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
595
596 if self.already_started.get() {
598 return;
599 }
600
601 let was_parser_inserted = self.parser_inserted.get();
606 self.parser_inserted.set(false);
607
608 let element = self.upcast::<Element>();
611 let asynch = element.has_attribute(&local_name!("async"));
612 if was_parser_inserted && !asynch {
614 self.non_blocking.set(true);
615 }
616
617 if self.prepare_the_script_text(cx).is_err() {
620 return;
621 }
622 let text: Cow<'_, str> = Cow::Owned(String::from(self.script_text.borrow().str()));
624 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
626 return;
627 }
628
629 if !self.upcast::<Node>().is_connected() {
631 return;
632 }
633
634 let script_type = if let Some(ty) = self.get_script_type() {
635 ty
637 } else {
638 return;
640 };
641
642 if was_parser_inserted {
646 self.parser_inserted.set(true);
647 self.non_blocking.set(false);
648 }
649
650 self.already_started.set(true);
652
653 let doc = self.owner_document();
655 self.preparation_time_document.set(Some(&doc));
656
657 if self.parser_inserted.get() && *self.parser_document != *doc {
661 return;
662 }
663
664 if !doc.scripting_enabled() {
666 return;
667 }
668
669 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
671 return;
672 }
673
674 let global = &doc.global();
675
676 if !element.has_attribute(&local_name!("src")) &&
678 global
679 .get_csp_list()
680 .should_elements_inline_type_behavior_be_blocked(
681 cx,
682 global,
683 element,
684 InlineCheckType::Script,
685 &text,
686 self.line_number as u32,
687 )
688 {
689 warn!("Blocking inline script due to CSP");
690 return;
691 }
692
693 if script_type == ScriptType::Classic {
695 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
696 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
697 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
698 let for_value = for_attribute.to_ascii_lowercase();
699 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
700 if for_value != "window" {
701 return;
702 }
703
704 let event_value = event_attribute.to_ascii_lowercase();
705 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
706 if event_value != "onload" && event_value != "onload()" {
707 return;
708 }
709 }
710 }
711
712 let encoding = element
717 .get_attribute_string_value(&local_name!("charset"))
718 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
719 .unwrap_or_else(|| doc.encoding());
720
721 let cors_setting = cors_setting_for_element(element);
723
724 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
726
727 let cryptographic_nonce =
732 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
733 element.nonce_value().trim().to_owned()
734 } else {
735 String::new()
736 };
737
738 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
741 let integrity_val_is_none = integrity_val.is_none();
742 let integrity_metadata = integrity_val.unwrap_or_default();
743
744 let referrer_policy = referrer_policy_for_element(element);
746
747 let parser_metadata = if self.parser_inserted.get() {
752 ParserMetadata::ParserInserted
753 } else {
754 ParserMetadata::NotParserInserted
755 };
756
757 let mut options = ScriptFetchOptions {
759 cryptographic_nonce,
760 integrity_metadata,
761 parser_metadata,
762 referrer_policy,
763 credentials_mode: module_credentials_mode,
764 render_blocking: false,
765 };
766
767 let base_url = doc.base_url();
770
771 let kind = self.get_script_kind(script_type);
772 let delayed_document = self.get_script_active_document(kind);
773
774 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
777 if script_type == ScriptType::ImportMap {
779 self.queue_error_event();
782 return;
783 }
784
785 if src.is_empty() {
787 self.queue_error_event();
788 return;
789 }
790
791 self.from_an_external_file.set(true);
793
794 let url = match base_url.join(&src) {
796 Ok(url) => url,
797 Err(_) => {
798 warn!("error parsing URL for script {}", src);
799 self.queue_error_event();
800 return;
801 },
802 };
803
804 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
806 self.marked_as_render_blocking.set(true);
807 doc.increment_render_blocking_element_count();
808 }
809
810 self.delay_load_event(&delayed_document, url.clone());
812
813 if self.marked_as_render_blocking.get() {
815 options.render_blocking = true;
816 }
817
818 match script_type {
820 ScriptType::Classic => {
821 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
823 },
824 ScriptType::Module => {
825 if integrity_val_is_none {
828 options.integrity_metadata = global
829 .import_map()
830 .resolve_a_module_integrity_metadata(&url);
831 }
832
833 let script = DomRoot::from_ref(self);
834
835 fetch_an_external_module_script(
837 cx,
838 url,
839 global,
840 options,
841 move |cx, module_tree| {
842 let load = module_tree.map(Script::Module).ok_or(());
843 *script.result.borrow_mut() = Some(load);
844
845 finish_fetching_a_script(&script, kind, cx);
846 },
847 );
848 },
849 ScriptType::ImportMap => (),
850 }
851 } else {
852 assert!(!text.is_empty());
855
856 match script_type {
858 ScriptType::Classic => {
859 let script = self.global().create_a_classic_script(
862 cx,
863 text,
864 base_url,
865 options,
866 ErrorReporting::Unmuted,
867 introduction_type,
868 self.line_number as u32,
869 false,
870 );
871 let result = Ok(Script::Classic(script));
872
873 if was_parser_inserted &&
874 doc.get_current_parser()
875 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
876 doc.has_a_stylesheet_that_is_blocking_scripts()
877 {
878 doc.set_pending_parsing_blocking_script(self, Some(result));
880 } else {
881 self.execute(cx, result);
883 }
884 return;
885 },
886 ScriptType::Module => {
887 self.delay_load_event(&delayed_document, base_url.clone());
889
890 if self.potentially_render_blocking() &&
892 doc.allows_adding_render_blocking_elements()
893 {
894 self.marked_as_render_blocking.set(true);
896 doc.increment_render_blocking_element_count();
897
898 options.render_blocking = true;
900 }
901
902 let script = DomRoot::from_ref(self);
903 fetch_inline_module_script(
906 cx,
907 global,
908 text,
909 base_url,
910 options,
911 self.line_number as u32,
912 introduction_type,
913 move |_, module_tree| {
914 let load = module_tree.map(Script::Module).ok_or(());
915 *script.result.borrow_mut() = Some(load);
916
917 let trusted = Trusted::new(&*script);
918
919 script
921 .owner_global()
922 .task_manager()
923 .networking_task_source()
924 .queue(task!(terminate_module_fetch: move |cx| {
925 finish_fetching_a_script(&trusted.root(), kind, cx);
927 }));
928 },
929 );
930 },
931 ScriptType::ImportMap => {
932 let import_map_result = parse_an_import_map_string(cx, global, &text, base_url);
935 let script = Script::ImportMap(import_map_result);
936
937 self.execute(cx, Ok(script));
939 return;
940 },
941 }
942 }
943
944 match kind {
946 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
947 ExternalScriptKind::ParsingBlocking => {
948 delayed_document.set_pending_parsing_blocking_script(self, None);
949 },
950 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
951 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
952 }
953 }
954
955 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
957 let doc = self.owner_document();
959
960 if *doc != *self.preparation_time_document.get().unwrap() {
962 return;
963 }
964
965 if self.marked_as_render_blocking.replace(false) {
967 self.marked_as_render_blocking.set(false);
968 doc.decrement_render_blocking_element_count();
969 }
970
971 let script = match result {
972 Err(_) => {
974 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
975 return;
976 },
977
978 Ok(script) => script,
979 };
980
981 let neutralized_doc =
985 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
986 let doc = self.owner_document();
987 doc.incr_ignore_destructive_writes_counter();
988 Some(doc)
989 } else {
990 None
991 };
992
993 let document = self.owner_document();
994
995 match script {
996 Script::Classic(script) => {
997 let old_script = document.GetCurrentScript();
999
1000 if self.upcast::<Node>().is_in_a_shadow_tree() {
1003 document.set_current_script(None)
1004 } else {
1005 document.set_current_script(Some(self))
1006 }
1007
1008 _ = self
1010 .owner_global()
1011 .run_a_classic_script(cx, script, RethrowErrors::No);
1012
1013 document.set_current_script(old_script.as_deref());
1015 },
1016 Script::Module(module_tree) => {
1017 document.set_current_script(None);
1019
1020 self.owner_global()
1022 .run_a_module_script(cx, module_tree, false);
1023 },
1024 Script::ImportMap(import_map) => {
1025 register_import_map(cx, &self.owner_global(), import_map);
1027 },
1028 }
1029
1030 if let Some(doc) = neutralized_doc {
1033 doc.decr_ignore_destructive_writes_counter();
1034 }
1035
1036 if self.from_an_external_file.get() {
1038 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1039 }
1040 }
1041
1042 pub(crate) fn queue_error_event(&self) {
1043 self.owner_global()
1044 .task_manager()
1045 .dom_manipulation_task_source()
1046 .queue_simple_event(self.upcast(), atom!("error"));
1047 }
1048
1049 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1051 let element = self.upcast::<Element>();
1052
1053 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1054 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1055
1056 match (type_attr, language_attr) {
1057 (Some(ty), _) if ty.is_empty() => {
1058 debug!("script type empty, inferring js");
1059 Some(ScriptType::Classic)
1060 },
1061 (None, Some(lang)) if lang.is_empty() => {
1062 debug!("script type empty, inferring js");
1063 Some(ScriptType::Classic)
1064 },
1065 (None, None) => {
1066 debug!("script type empty, inferring js");
1067 Some(ScriptType::Classic)
1068 },
1069 (None, Some(lang)) => {
1070 debug!("script language={}", lang);
1071 let language = format!("text/{}", lang);
1072
1073 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1074 Some(ScriptType::Classic)
1075 } else {
1076 None
1077 }
1078 },
1079 (Some(ty), _) => {
1080 debug!("script type={}", ty);
1081
1082 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1083 return Some(ScriptType::Module);
1084 }
1085
1086 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1087 return Some(ScriptType::ImportMap);
1088 }
1089
1090 if SCRIPT_JS_MIMES
1091 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1092 {
1093 Some(ScriptType::Classic)
1094 } else {
1095 None
1096 }
1097 },
1098 }
1099 }
1100
1101 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1102 self.parser_inserted.set(parser_inserted);
1103 }
1104
1105 pub(crate) fn set_already_started(&self, already_started: bool) {
1106 self.already_started.set(already_started);
1107 }
1108
1109 fn text(&self) -> DOMString {
1110 match self.Text() {
1111 TrustedScriptOrString::String(value) => value,
1112 TrustedScriptOrString::TrustedScript(trusted_script) => {
1113 DOMString::from(trusted_script.to_string())
1114 },
1115 }
1116 }
1117}
1118
1119impl VirtualMethods for HTMLScriptElement {
1120 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1121 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1122 }
1123
1124 fn attribute_mutated(
1125 &self,
1126 cx: &mut js::context::JSContext,
1127 attr: AttrRef<'_>,
1128 mutation: AttributeMutation,
1129 ) {
1130 self.super_type()
1131 .unwrap()
1132 .attribute_mutated(cx, attr, mutation);
1133 if *attr.local_name() == local_name!("src") {
1134 if let AttributeMutation::Set(..) = mutation &&
1135 !self.parser_inserted.get() &&
1136 self.upcast::<Node>().is_connected()
1137 {
1138 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1139 }
1140 } else if *attr.local_name() == local_name!("blocking") &&
1141 !self.has_render_blocking_attribute() &&
1142 self.marked_as_render_blocking.replace(false)
1143 {
1144 let document = self.owner_document();
1145 document.decrement_render_blocking_element_count();
1146 }
1147 }
1148
1149 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1151 if let Some(s) = self.super_type() {
1152 s.children_changed(cx, mutation);
1153 }
1154
1155 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1156 let script = DomRoot::from_ref(self);
1157 self.owner_document().add_delayed_task(
1161 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1162 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1163 }),
1164 );
1165 }
1166 }
1167
1168 fn post_connection_steps(&self, cx: &mut JSContext) {
1170 if let Some(s) = self.super_type() {
1171 s.post_connection_steps(cx);
1172 }
1173
1174 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1175 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1176 }
1177 }
1178
1179 fn cloning_steps(
1180 &self,
1181 cx: &mut JSContext,
1182 copy: &Node,
1183 maybe_doc: Option<&Document>,
1184 clone_children: CloneChildrenFlag,
1185 ) {
1186 if let Some(s) = self.super_type() {
1187 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1188 }
1189
1190 if self.already_started.get() {
1192 copy.downcast::<HTMLScriptElement>()
1193 .unwrap()
1194 .set_already_started(true);
1195 }
1196 }
1197
1198 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1199 self.super_type().unwrap().unbind_from_tree(cx, context);
1200
1201 if self.marked_as_render_blocking.replace(false) {
1202 let document = self.owner_document();
1203 document.decrement_render_blocking_element_count();
1204 }
1205 }
1206}
1207
1208impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1209 fn Src(&self) -> TrustedScriptURLOrUSVString {
1211 let element = self.upcast::<Element>();
1212 element.get_trusted_type_url_attribute(&local_name!("src"))
1213 }
1214
1215 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1217 let element = self.upcast::<Element>();
1218 let local_name = &local_name!("src");
1219 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1220 cx,
1221 &element.owner_global(),
1222 value,
1223 &format!("HTMLScriptElement {}", local_name),
1224 )?;
1225 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1226 Ok(())
1227 }
1228
1229 make_getter!(Type, "type");
1231 make_setter!(SetType, "type");
1233
1234 make_getter!(Charset, "charset");
1236 make_setter!(SetCharset, "charset");
1238
1239 fn Async(&self) -> bool {
1241 self.non_blocking.get() ||
1242 self.upcast::<Element>()
1243 .has_attribute(&local_name!("async"))
1244 }
1245
1246 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1248 self.non_blocking.set(false);
1249 self.upcast::<Element>()
1250 .set_bool_attribute(cx, &local_name!("async"), value);
1251 }
1252
1253 make_bool_getter!(Defer, "defer");
1255 make_bool_setter!(SetDefer, "defer");
1257
1258 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1260 self.blocking.or_init(|| {
1261 DOMTokenList::new(
1262 cx,
1263 self.upcast(),
1264 &local_name!("blocking"),
1265 Some(vec![Atom::from("render")]),
1266 )
1267 })
1268 }
1269
1270 make_bool_getter!(NoModule, "nomodule");
1272 make_bool_setter!(SetNoModule, "nomodule");
1274
1275 make_getter!(Integrity, "integrity");
1277 make_setter!(SetIntegrity, "integrity");
1279
1280 make_getter!(Event, "event");
1282 make_setter!(SetEvent, "event");
1284
1285 make_getter!(HtmlFor, "for");
1287 make_setter!(SetHtmlFor, "for");
1289
1290 fn GetCrossOrigin(&self) -> Option<DOMString> {
1292 reflect_cross_origin_attribute(self.upcast::<Element>())
1293 }
1294
1295 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1297 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1298 }
1299
1300 fn ReferrerPolicy(&self) -> DOMString {
1302 reflect_referrer_policy_attribute(self.upcast::<Element>())
1303 }
1304
1305 make_setter!(SetReferrerPolicy, "referrerpolicy");
1307
1308 fn InnerText(&self) -> TrustedScriptOrString {
1310 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1312 }
1313
1314 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1316 let value = TrustedScript::get_trusted_type_compliant_string(
1319 cx,
1320 &self.owner_global(),
1321 input,
1322 "HTMLScriptElement innerText",
1323 )?;
1324 *self.script_text.borrow_mut() = value.clone();
1325 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1327 Ok(())
1328 }
1329
1330 fn Text(&self) -> TrustedScriptOrString {
1332 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1333 }
1334
1335 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1337 let value = TrustedScript::get_trusted_type_compliant_string(
1340 cx,
1341 &self.owner_global(),
1342 value,
1343 "HTMLScriptElement text",
1344 )?;
1345 *self.script_text.borrow_mut() = value.clone();
1347 Node::string_replace_all(cx, value, self.upcast::<Node>());
1349 Ok(())
1350 }
1351
1352 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1354 Some(TrustedScriptOrString::String(
1356 self.upcast::<Node>().GetTextContent()?,
1357 ))
1358 }
1359
1360 fn SetTextContent(
1362 &self,
1363 cx: &mut JSContext,
1364 value: Option<TrustedScriptOrString>,
1365 ) -> Fallible<()> {
1366 let value = TrustedScript::get_trusted_type_compliant_string(
1369 cx,
1370 &self.owner_global(),
1371 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1372 "HTMLScriptElement textContent",
1373 )?;
1374 *self.script_text.borrow_mut() = value.clone();
1376 self.upcast::<Node>()
1378 .set_text_content_for_element(cx, Some(value));
1379 Ok(())
1380 }
1381
1382 fn Supports(_window: &Window, type_: DOMString) -> bool {
1384 matches!(&*type_.str(), "classic" | "module" | "importmap")
1387 }
1388}
1389
1390pub fn substitute_with_local_script(script_source: &str, script: &mut Cow<'_, str>, url: ServoUrl) {
1391 let mut path = PathBuf::from(script_source);
1392 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1393 debug!("Attempting to read script stored at: {:?}", path);
1394 match read_to_string(path.clone()) {
1395 Ok(local_script) => {
1396 debug!("Found script stored at: {:?}", path);
1397 *script = Cow::Owned(local_script);
1398 },
1399 Err(why) => warn!("Could not restore script from file {:?}", why),
1400 }
1401}
1402
1403#[derive(Clone, Copy)]
1404enum ExternalScriptKind {
1405 Deferred,
1406 ParsingBlocking,
1407 AsapInOrder,
1408 Asap,
1409}