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::blob_url_store::UrlWithBlobClaim;
18use net_traits::http_status::HttpStatus;
19use net_traits::request::{
20 CorsSettings, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId,
21};
22use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
23use script_bindings::cell::DomRefCell;
24use servo_base::id::WebViewId;
25use servo_url::ServoUrl;
26use style::attr::AttrValue;
27use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
28use stylo_atoms::Atom;
29
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::event_loop::document_loader::{LoadBlocker, LoadType};
63use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request_with_claim};
64use crate::modules::import_map::{ImportMap, parse_an_import_map_string, register_import_map};
65use crate::modules::script_module::{
66 ModuleTree, ScriptFetchOptions, fetch_an_external_module_script, fetch_inline_module_script,
67};
68use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
69use crate::script_runtime::IntroductionType;
70use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
71
72#[dom_struct]
73pub(crate) struct HTMLScriptElement {
74 htmlelement: HTMLElement,
75
76 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
78
79 already_started: Cell<bool>,
81
82 parser_inserted: Cell<bool>,
84
85 non_blocking: Cell<bool>,
89
90 parser_document: Dom<Document>,
93
94 preparation_time_document: MutNullableDom<Document>,
97
98 line_number: u64,
100
101 script_text: DomRefCell<DOMString>,
103
104 from_an_external_file: Cell<bool>,
106
107 blocking: MutNullableDom<DOMTokenList>,
109
110 marked_as_render_blocking: Cell<bool>,
113
114 result: DomRefCell<Option<ScriptResult>>,
116}
117
118impl HTMLScriptElement {
119 fn new_inherited(
120 local_name: LocalName,
121 prefix: Option<Prefix>,
122 document: &Document,
123 creator: ElementCreator,
124 ) -> HTMLScriptElement {
125 HTMLScriptElement {
126 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
127 already_started: Cell::new(false),
128 delaying_the_load_event: Default::default(),
129 parser_inserted: Cell::new(creator.is_parser_created()),
130 non_blocking: Cell::new(!creator.is_parser_created()),
131 parser_document: Dom::from_ref(document),
132 preparation_time_document: MutNullableDom::new(None),
133 line_number: creator.return_line_number(),
134 script_text: DomRefCell::new(DOMString::new()),
135 from_an_external_file: Cell::new(false),
136 blocking: Default::default(),
137 marked_as_render_blocking: Default::default(),
138 result: DomRefCell::new(None),
139 }
140 }
141
142 pub(crate) fn new(
143 cx: &mut js::context::JSContext,
144 local_name: LocalName,
145 prefix: Option<Prefix>,
146 document: &Document,
147 proto: Option<HandleObject>,
148 creator: ElementCreator,
149 ) -> DomRoot<HTMLScriptElement> {
150 Node::reflect_node_with_proto(
151 cx,
152 Box::new(HTMLScriptElement::new_inherited(
153 local_name, prefix, document, creator,
154 )),
155 document,
156 proto,
157 )
158 }
159
160 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
165 debug_assert!(self.delaying_the_load_event.borrow().is_none());
166
167 *self.delaying_the_load_event.borrow_mut() =
168 Some(LoadBlocker::new(document, LoadType::Script(url)));
169 }
170
171 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
178 let element = self.upcast::<Element>();
179
180 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
181 ExternalScriptKind::Asap
182 } else if !self.parser_inserted.get() {
183 ExternalScriptKind::AsapInOrder
184 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
185 {
186 ExternalScriptKind::Deferred
187 } else {
188 ExternalScriptKind::ParsingBlocking
189 }
190 }
191
192 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
194 match script_kind {
195 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
196 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
197 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
198 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
199 }
200 }
201}
202
203pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
206 "application/ecmascript",
207 "application/javascript",
208 "application/x-ecmascript",
209 "application/x-javascript",
210 "text/ecmascript",
211 "text/javascript",
212 "text/javascript1.0",
213 "text/javascript1.1",
214 "text/javascript1.2",
215 "text/javascript1.3",
216 "text/javascript1.4",
217 "text/javascript1.5",
218 "text/jscript",
219 "text/livescript",
220 "text/x-ecmascript",
221 "text/x-javascript",
222];
223
224#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
225pub(crate) enum ScriptType {
226 Classic,
227 Module,
228 ImportMap,
229}
230
231fn finish_fetching_a_script(
233 elem: &HTMLScriptElement,
234 script_kind: ExternalScriptKind,
235 cx: &mut JSContext,
236) {
237 let load = elem.result.take().expect("Result must be ready to proceed");
238
239 match script_kind {
241 ExternalScriptKind::Asap => {
242 let document = elem.preparation_time_document.get().unwrap();
243 document.asap_script_loaded(cx, elem, load)
244 },
245 ExternalScriptKind::AsapInOrder => {
246 let document = elem.preparation_time_document.get().unwrap();
247 document.asap_in_order_script_loaded(cx, elem, load)
248 },
249 ExternalScriptKind::Deferred => {
250 let document = elem.parser_document.as_rooted();
251 document.deferred_script_loaded(cx, elem, load);
252 },
253 ExternalScriptKind::ParsingBlocking => {
254 let document = elem.parser_document.as_rooted();
255 document.pending_parsing_blocking_script_loaded(elem, load, cx);
256 },
257 }
258
259 LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
261}
262
263pub(crate) type ScriptResult = Result<Script, ()>;
264
265#[derive(JSTraceable, MallocSizeOf)]
267pub(crate) enum Script {
268 Classic(ClassicScript),
269 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
270 ImportMap(Fallible<ImportMap>),
271}
272
273struct ClassicContext {
275 elem: Trusted<HTMLScriptElement>,
277 kind: ExternalScriptKind,
279 character_encoding: &'static Encoding,
282 data: Vec<u8>,
284 metadata: Option<Metadata>,
286 url: UrlWithBlobClaim,
288 status: Result<(), NetworkError>,
290 fetch_options: ScriptFetchOptions,
292 response_was_cors_cross_origin: bool,
294}
295
296impl FetchResponseListener for ClassicContext {
297 fn process_request_body(&mut self, _: RequestId) {}
299
300 fn process_response(
301 &mut self,
302 _: &mut js::context::JSContext,
303 _: RequestId,
304 metadata: Result<FetchMetadata, NetworkError>,
305 ) {
306 self.metadata = metadata.ok().map(|meta| {
307 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
308 match meta {
309 FetchMetadata::Unfiltered(m) => m,
310 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
311 }
312 });
313
314 let status = self
315 .metadata
316 .as_ref()
317 .map(|m| m.status.clone())
318 .unwrap_or_else(HttpStatus::new_error);
319
320 self.status = {
321 if status.is_error() {
322 Err(NetworkError::ResourceLoadError(
323 "No http status code received".to_owned(),
324 ))
325 } else if status.is_success() {
326 Ok(())
327 } else {
328 Err(NetworkError::ResourceLoadError(format!(
329 "HTTP error code {}",
330 status.code()
331 )))
332 }
333 };
334 }
335
336 fn process_response_chunk(
337 &mut self,
338 _: &mut js::context::JSContext,
339 _: RequestId,
340 mut chunk: Vec<u8>,
341 ) {
342 if self.status.is_ok() {
343 self.data.append(&mut chunk);
344 }
345 }
346
347 fn process_response_eof(
350 mut self,
351 cx: &mut js::context::JSContext,
352 _: RequestId,
353 response: Result<(), NetworkError>,
354 timing: ResourceFetchTiming,
355 ) {
356 network_listener::submit_timing(cx, &self, &response, &timing);
358
359 let elem = self.elem.root();
360
361 match (response.as_ref(), self.status.as_ref()) {
362 (Err(error), _) | (_, Err(error)) => {
363 error!(
364 "Fetching classic script failed {:?} ({:?})",
365 error, self.url
366 );
367 *elem.result.borrow_mut() = Some(Err(()));
369 finish_fetching_a_script(&elem, self.kind, cx);
370 return;
371 },
372 _ => {},
373 };
374
375 let metadata = self.metadata.take().unwrap();
376 let final_url = metadata.final_url;
377
378 let encoding = metadata
381 .charset
382 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
383 .unwrap_or(self.character_encoding);
384
385 let (mut source_text, _, _) = encoding.decode(&self.data);
387
388 let global = elem.global();
389
390 if let Some(window) = global.downcast::<Window>() &&
391 let Some(script_source) = window.local_script_source()
392 {
393 substitute_with_local_script(script_source, &mut source_text, final_url.clone());
394 }
395
396 let muted_errors = self.response_was_cors_cross_origin;
398
399 let script = global.create_a_classic_script(
402 cx,
403 source_text,
404 final_url,
405 self.fetch_options.clone(),
406 ErrorReporting::from(muted_errors),
407 Some(IntroductionType::SRC_SCRIPT),
408 1,
409 true,
410 );
411
412 *elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
443 finish_fetching_a_script(&elem, self.kind, cx);
444 }
446
447 fn process_csp_violations(
448 &mut self,
449 cx: &mut js::context::JSContext,
450 _request_id: RequestId,
451 violations: Vec<Violation>,
452 ) {
453 let global = &self.resource_timing_global();
454 let elem = self.elem.root();
455 global.report_csp_violations(cx, violations, Some(elem.upcast()), None);
456 }
457
458 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
459 self.data.reserve(size - self.data.len());
460 }
461}
462
463impl ResourceTimingListener for ClassicContext {
464 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
465 let initiator_type = InitiatorType::LocalName(
466 self.elem
467 .root()
468 .upcast::<Element>()
469 .local_name()
470 .to_string(),
471 );
472 (initiator_type, self.url.url())
473 }
474
475 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
476 self.elem.root().owner_document().global()
477 }
478}
479
480#[allow(clippy::too_many_arguments)]
483pub(crate) fn script_fetch_request(
484 webview_id: WebViewId,
485 url: UrlWithBlobClaim,
486 cors_setting: Option<CorsSettings>,
487 options: ScriptFetchOptions,
488 referrer: Referrer,
489) -> RequestBuilder {
490 create_a_potential_cors_request_with_claim(
493 Some(webview_id),
494 url,
495 Destination::Script,
496 cors_setting,
497 None,
498 referrer,
499 )
500 .parser_metadata(options.parser_metadata)
501 .integrity_metadata(options.integrity_metadata.clone())
502 .referrer_policy(options.referrer_policy)
503 .cryptographic_nonce_metadata(options.cryptographic_nonce)
504}
505
506fn fetch_a_classic_script(
508 script: &HTMLScriptElement,
509 kind: ExternalScriptKind,
510 url: UrlWithBlobClaim,
511 cors_setting: Option<CorsSettings>,
512 options: ScriptFetchOptions,
513 character_encoding: &'static Encoding,
514) {
515 let doc = script.owner_document();
517 let global = script.global();
518 let referrer = global.get_referrer();
519 let request = script_fetch_request(
520 doc.webview_id(),
521 url.clone(),
522 cors_setting,
523 options.clone(),
524 referrer,
525 )
526 .with_global_scope(&global);
527
528 let context = ClassicContext {
531 elem: Trusted::new(script),
532 kind,
533 character_encoding,
534 data: vec![],
535 metadata: None,
536 url,
537 status: Ok(()),
538 fetch_options: options,
539 response_was_cors_cross_origin: false,
540 };
541 doc.fetch_background(request, context);
542}
543
544impl HTMLScriptElement {
545 pub(crate) fn set_initial_script_text(&self) {
547 *self.script_text.borrow_mut() = self.text();
548 }
549
550 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
552 if *self.script_text.borrow() != self.text() {
556 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
557 cx,
558 &self.owner_global(),
559 self.Text(),
560 "HTMLScriptElement text",
561 )?;
562 }
563
564 Ok(())
565 }
566
567 fn has_render_blocking_attribute(&self) -> bool {
568 self.blocking
569 .get()
570 .is_some_and(|list| list.Contains("render".into()))
571 }
572
573 fn potentially_render_blocking(&self) -> bool {
575 if self.has_render_blocking_attribute() {
579 return true;
580 }
581 let element = self.upcast::<Element>();
582 self.get_script_type()
586 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
587 self.parser_inserted.get() &&
588 !element.has_attribute(&local_name!("async")) &&
589 !element.has_attribute(&local_name!("defer"))
590 }
591
592 pub(crate) fn prepare(
594 &self,
595 cx: &mut JSContext,
596 introduction_type_override: Option<&'static CStr>,
597 ) {
598 let introduction_type =
599 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
600
601 if self.already_started.get() {
603 return;
604 }
605
606 let was_parser_inserted = self.parser_inserted.get();
611 self.parser_inserted.set(false);
612
613 let element = self.upcast::<Element>();
616 let asynch = element.has_attribute(&local_name!("async"));
617 if was_parser_inserted && !asynch {
619 self.non_blocking.set(true);
620 }
621
622 if self.prepare_the_script_text(cx).is_err() {
625 return;
626 }
627 let text: Cow<'_, str> = Cow::Owned(String::from(self.script_text.borrow().str()));
629 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
631 return;
632 }
633
634 if !self.upcast::<Node>().is_connected() {
636 return;
637 }
638
639 let script_type = if let Some(ty) = self.get_script_type() {
640 ty
642 } else {
643 return;
645 };
646
647 if was_parser_inserted {
651 self.parser_inserted.set(true);
652 self.non_blocking.set(false);
653 }
654
655 self.already_started.set(true);
657
658 let doc = self.owner_document();
660 self.preparation_time_document.set(Some(&doc));
661
662 if self.parser_inserted.get() && *self.parser_document != *doc {
666 return;
667 }
668
669 if !doc.scripting_enabled() {
671 return;
672 }
673
674 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
676 return;
677 }
678
679 let global = &doc.global();
680
681 if !element.has_attribute(&local_name!("src")) &&
683 global
684 .get_csp_list()
685 .should_elements_inline_type_behavior_be_blocked(
686 cx,
687 global,
688 element,
689 InlineCheckType::Script,
690 &text,
691 self.line_number as u32,
692 )
693 {
694 warn!("Blocking inline script due to CSP");
695 return;
696 }
697
698 if script_type == ScriptType::Classic {
700 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
701 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
702 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
703 let for_value = for_attribute.to_ascii_lowercase();
704 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
705 if for_value != "window" {
706 return;
707 }
708
709 let event_value = event_attribute.to_ascii_lowercase();
710 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
711 if event_value != "onload" && event_value != "onload()" {
712 return;
713 }
714 }
715 }
716
717 let encoding = element
722 .get_attribute_string_value(&local_name!("charset"))
723 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
724 .unwrap_or_else(|| doc.encoding());
725
726 let cors_setting = cors_setting_for_element(element);
728
729 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
731
732 let cryptographic_nonce =
737 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
738 element.nonce_value().trim().to_owned()
739 } else {
740 String::new()
741 };
742
743 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
746 let integrity_val_is_none = integrity_val.is_none();
747 let integrity_metadata = integrity_val.unwrap_or_default();
748
749 let referrer_policy = referrer_policy_for_element(element);
751
752 let parser_metadata = if self.parser_inserted.get() {
757 ParserMetadata::ParserInserted
758 } else {
759 ParserMetadata::NotParserInserted
760 };
761
762 let mut options = ScriptFetchOptions {
764 cryptographic_nonce,
765 integrity_metadata,
766 parser_metadata,
767 referrer_policy,
768 credentials_mode: module_credentials_mode,
769 render_blocking: false,
770 };
771
772 let base_url = doc.base_url();
775
776 let kind = self.get_script_kind(script_type);
777 let delayed_document = self.get_script_active_document(kind);
778
779 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
782 if script_type == ScriptType::ImportMap {
784 self.queue_error_event();
787 return;
788 }
789
790 if src.is_empty() {
792 self.queue_error_event();
793 return;
794 }
795
796 self.from_an_external_file.set(true);
798
799 let url = match base_url.join(&src) {
801 Ok(url) => url,
802 Err(_) => {
803 warn!("error parsing URL for script {}", src);
804 self.queue_error_event();
805 return;
806 },
807 };
808 let url = ensure_blob_referenced_by_url_is_kept_alive(global, url);
809
810 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
812 self.marked_as_render_blocking.set(true);
813 doc.increment_render_blocking_element_count();
814 }
815
816 self.delay_load_event(&delayed_document, url.url());
818
819 if self.marked_as_render_blocking.get() {
821 options.render_blocking = true;
822 }
823
824 match script_type {
826 ScriptType::Classic => {
827 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
829 },
830 ScriptType::Module => {
831 if integrity_val_is_none {
834 options.integrity_metadata = global
835 .import_map()
836 .resolve_a_module_integrity_metadata(&url.url());
837 }
838
839 let script = DomRoot::from_ref(self);
840
841 fetch_an_external_module_script(
843 cx,
844 url,
845 global,
846 options,
847 move |cx, module_tree| {
848 let load = module_tree.map(Script::Module).ok_or(());
849 *script.result.borrow_mut() = Some(load);
850
851 finish_fetching_a_script(&script, kind, cx);
852 },
853 );
854 },
855 ScriptType::ImportMap => (),
856 }
857 } else {
858 assert!(!text.is_empty());
861
862 match script_type {
864 ScriptType::Classic => {
865 let script = self.global().create_a_classic_script(
868 cx,
869 text,
870 base_url,
871 options,
872 ErrorReporting::Unmuted,
873 introduction_type,
874 self.line_number as u32,
875 false,
876 );
877 let result = Ok(Script::Classic(script));
878
879 if was_parser_inserted &&
880 doc.get_current_parser()
881 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
882 doc.has_a_stylesheet_that_is_blocking_scripts()
883 {
884 doc.set_pending_parsing_blocking_script(self, Some(result));
886 } else {
887 self.execute(cx, result);
889 }
890 return;
891 },
892 ScriptType::Module => {
893 self.delay_load_event(&delayed_document, base_url.clone());
895
896 if self.potentially_render_blocking() &&
898 doc.allows_adding_render_blocking_elements()
899 {
900 self.marked_as_render_blocking.set(true);
902 doc.increment_render_blocking_element_count();
903
904 options.render_blocking = true;
906 }
907
908 let script = DomRoot::from_ref(self);
909 fetch_inline_module_script(
912 cx,
913 global,
914 text,
915 base_url,
916 options,
917 self.line_number as u32,
918 introduction_type,
919 move |_, module_tree| {
920 let load = module_tree.map(Script::Module).ok_or(());
921 *script.result.borrow_mut() = Some(load);
922
923 let trusted = Trusted::new(&*script);
924
925 script
927 .owner_global()
928 .task_manager()
929 .networking_task_source()
930 .queue(task!(terminate_module_fetch: move |cx| {
931 finish_fetching_a_script(&trusted.root(), kind, cx);
933 }));
934 },
935 );
936 },
937 ScriptType::ImportMap => {
938 let import_map_result = parse_an_import_map_string(cx, global, &text, base_url);
941 let script = Script::ImportMap(import_map_result);
942
943 self.execute(cx, Ok(script));
945 return;
946 },
947 }
948 }
949
950 match kind {
952 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
953 ExternalScriptKind::ParsingBlocking => {
954 delayed_document.set_pending_parsing_blocking_script(self, None);
955 },
956 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
957 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
958 }
959 }
960
961 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
963 let doc = self.owner_document();
965
966 if *doc != *self.preparation_time_document.get().unwrap() {
968 return;
969 }
970
971 if self.marked_as_render_blocking.replace(false) {
973 self.marked_as_render_blocking.set(false);
974 doc.decrement_render_blocking_element_count();
975 }
976
977 let script = match result {
978 Err(_) => {
980 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
981 return;
982 },
983
984 Ok(script) => script,
985 };
986
987 let neutralized_doc =
991 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
992 let doc = self.owner_document();
993 doc.incr_ignore_destructive_writes_counter();
994 Some(doc)
995 } else {
996 None
997 };
998
999 let document = self.owner_document();
1000
1001 match script {
1002 Script::Classic(script) => {
1003 let old_script = document.GetCurrentScript();
1005
1006 if self.upcast::<Node>().is_in_a_shadow_tree() {
1009 document.set_current_script(None)
1010 } else {
1011 document.set_current_script(Some(self))
1012 }
1013
1014 _ = self
1016 .owner_global()
1017 .run_a_classic_script(cx, script, RethrowErrors::No);
1018
1019 document.set_current_script(old_script.as_deref());
1021 },
1022 Script::Module(module_tree) => {
1023 document.set_current_script(None);
1025
1026 self.owner_global()
1028 .run_a_module_script(cx, module_tree, false);
1029 },
1030 Script::ImportMap(import_map) => {
1031 register_import_map(cx, &self.owner_global(), import_map);
1033 },
1034 }
1035
1036 if let Some(doc) = neutralized_doc {
1039 doc.decr_ignore_destructive_writes_counter();
1040 }
1041
1042 if self.from_an_external_file.get() {
1044 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1045 }
1046 }
1047
1048 pub(crate) fn queue_error_event(&self) {
1049 self.owner_global()
1050 .task_manager()
1051 .dom_manipulation_task_source()
1052 .queue_simple_event(self.upcast(), atom!("error"));
1053 }
1054
1055 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1057 let element = self.upcast::<Element>();
1058
1059 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1060 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1061
1062 match (type_attr, language_attr) {
1063 (Some(ty), _) if ty.is_empty() => {
1064 debug!("script type empty, inferring js");
1065 Some(ScriptType::Classic)
1066 },
1067 (None, Some(lang)) if lang.is_empty() => {
1068 debug!("script type empty, inferring js");
1069 Some(ScriptType::Classic)
1070 },
1071 (None, None) => {
1072 debug!("script type empty, inferring js");
1073 Some(ScriptType::Classic)
1074 },
1075 (None, Some(lang)) => {
1076 debug!("script language={}", lang);
1077 let language = format!("text/{}", lang);
1078
1079 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1080 Some(ScriptType::Classic)
1081 } else {
1082 None
1083 }
1084 },
1085 (Some(ty), _) => {
1086 debug!("script type={}", ty);
1087
1088 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1089 return Some(ScriptType::Module);
1090 }
1091
1092 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1093 return Some(ScriptType::ImportMap);
1094 }
1095
1096 if SCRIPT_JS_MIMES
1097 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1098 {
1099 Some(ScriptType::Classic)
1100 } else {
1101 None
1102 }
1103 },
1104 }
1105 }
1106
1107 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1108 self.parser_inserted.set(parser_inserted);
1109 }
1110
1111 pub(crate) fn set_already_started(&self, already_started: bool) {
1112 self.already_started.set(already_started);
1113 }
1114
1115 fn text(&self) -> DOMString {
1116 match self.Text() {
1117 TrustedScriptOrString::String(value) => value,
1118 TrustedScriptOrString::TrustedScript(trusted_script) => {
1119 DOMString::from(trusted_script.to_string())
1120 },
1121 }
1122 }
1123}
1124
1125impl VirtualMethods for HTMLScriptElement {
1126 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1127 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1128 }
1129
1130 fn attribute_mutated(
1131 &self,
1132 cx: &mut js::context::JSContext,
1133 attr: AttrRef<'_>,
1134 mutation: AttributeMutation,
1135 ) {
1136 self.super_type()
1137 .unwrap()
1138 .attribute_mutated(cx, attr, mutation);
1139 if *attr.local_name() == local_name!("src") {
1140 if let AttributeMutation::Set(..) = mutation &&
1141 !self.parser_inserted.get() &&
1142 self.upcast::<Node>().is_connected()
1143 {
1144 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1145 }
1146 } else if *attr.local_name() == local_name!("blocking") &&
1147 !self.has_render_blocking_attribute() &&
1148 self.marked_as_render_blocking.replace(false)
1149 {
1150 let document = self.owner_document();
1151 document.decrement_render_blocking_element_count();
1152 }
1153 }
1154
1155 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1157 if let Some(s) = self.super_type() {
1158 s.children_changed(cx, mutation);
1159 }
1160
1161 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1162 let script = DomRoot::from_ref(self);
1163 self.owner_document().add_delayed_task(
1167 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1168 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1169 }),
1170 );
1171 }
1172 }
1173
1174 fn post_connection_steps(&self, cx: &mut JSContext) {
1176 if let Some(s) = self.super_type() {
1177 s.post_connection_steps(cx);
1178 }
1179
1180 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1181 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1182 }
1183 }
1184
1185 fn cloning_steps(
1186 &self,
1187 cx: &mut JSContext,
1188 copy: &Node,
1189 maybe_doc: Option<&Document>,
1190 clone_children: CloneChildrenFlag,
1191 ) {
1192 if let Some(s) = self.super_type() {
1193 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1194 }
1195
1196 if self.already_started.get() {
1198 copy.downcast::<HTMLScriptElement>()
1199 .unwrap()
1200 .set_already_started(true);
1201 }
1202 }
1203
1204 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1205 self.super_type().unwrap().unbind_from_tree(cx, context);
1206
1207 if self.marked_as_render_blocking.replace(false) {
1208 let document = self.owner_document();
1209 document.decrement_render_blocking_element_count();
1210 }
1211 }
1212}
1213
1214impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1215 fn Src(&self) -> TrustedScriptURLOrUSVString {
1217 let element = self.upcast::<Element>();
1218 element.get_trusted_type_url_attribute(&local_name!("src"))
1219 }
1220
1221 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1223 let element = self.upcast::<Element>();
1224 let local_name = &local_name!("src");
1225 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1226 cx,
1227 &element.owner_global(),
1228 value,
1229 &format!("HTMLScriptElement {}", local_name),
1230 )?;
1231 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1232 Ok(())
1233 }
1234
1235 make_getter!(Type, "type");
1237 make_setter!(SetType, "type");
1239
1240 make_getter!(Charset, "charset");
1242 make_setter!(SetCharset, "charset");
1244
1245 fn Async(&self) -> bool {
1247 self.non_blocking.get() ||
1248 self.upcast::<Element>()
1249 .has_attribute(&local_name!("async"))
1250 }
1251
1252 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1254 self.non_blocking.set(false);
1255 self.upcast::<Element>()
1256 .set_bool_attribute(cx, &local_name!("async"), value);
1257 }
1258
1259 make_bool_getter!(Defer, "defer");
1261 make_bool_setter!(SetDefer, "defer");
1263
1264 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1266 self.blocking.or_init(|| {
1267 DOMTokenList::new(
1268 cx,
1269 self.upcast(),
1270 &local_name!("blocking"),
1271 Some(vec![Atom::from("render")]),
1272 )
1273 })
1274 }
1275
1276 make_bool_getter!(NoModule, "nomodule");
1278 make_bool_setter!(SetNoModule, "nomodule");
1280
1281 make_getter!(Integrity, "integrity");
1283 make_setter!(SetIntegrity, "integrity");
1285
1286 make_getter!(Event, "event");
1288 make_setter!(SetEvent, "event");
1290
1291 make_getter!(HtmlFor, "for");
1293 make_setter!(SetHtmlFor, "for");
1295
1296 fn GetCrossOrigin(&self) -> Option<DOMString> {
1298 reflect_cross_origin_attribute(self.upcast::<Element>())
1299 }
1300
1301 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1303 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1304 }
1305
1306 fn ReferrerPolicy(&self) -> DOMString {
1308 reflect_referrer_policy_attribute(self.upcast::<Element>())
1309 }
1310
1311 make_setter!(SetReferrerPolicy, "referrerpolicy");
1313
1314 fn InnerText(&self) -> TrustedScriptOrString {
1316 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1318 }
1319
1320 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1322 let value = TrustedScript::get_trusted_type_compliant_string(
1325 cx,
1326 &self.owner_global(),
1327 input,
1328 "HTMLScriptElement innerText",
1329 )?;
1330 *self.script_text.borrow_mut() = value.clone();
1331 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1333 Ok(())
1334 }
1335
1336 fn Text(&self) -> TrustedScriptOrString {
1338 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1339 }
1340
1341 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1343 let value = TrustedScript::get_trusted_type_compliant_string(
1346 cx,
1347 &self.owner_global(),
1348 value,
1349 "HTMLScriptElement text",
1350 )?;
1351 *self.script_text.borrow_mut() = value.clone();
1353 Node::string_replace_all(cx, value, self.upcast::<Node>());
1355 Ok(())
1356 }
1357
1358 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1360 Some(TrustedScriptOrString::String(
1362 self.upcast::<Node>().GetTextContent()?,
1363 ))
1364 }
1365
1366 fn SetTextContent(
1368 &self,
1369 cx: &mut JSContext,
1370 value: Option<TrustedScriptOrString>,
1371 ) -> Fallible<()> {
1372 let value = TrustedScript::get_trusted_type_compliant_string(
1375 cx,
1376 &self.owner_global(),
1377 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1378 "HTMLScriptElement textContent",
1379 )?;
1380 *self.script_text.borrow_mut() = value.clone();
1382 self.upcast::<Node>()
1384 .set_text_content_for_element(cx, Some(value));
1385 Ok(())
1386 }
1387
1388 fn Supports(_window: &Window, type_: DOMString) -> bool {
1390 matches!(&*type_.str(), "classic" | "module" | "importmap")
1393 }
1394}
1395
1396pub fn substitute_with_local_script(script_source: &str, script: &mut Cow<'_, str>, url: ServoUrl) {
1397 let mut path = PathBuf::from(script_source);
1398 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1399 debug!("Attempting to read script stored at: {:?}", path);
1400 match read_to_string(path.clone()) {
1401 Ok(local_script) => {
1402 debug!("Found script stored at: {:?}", path);
1403 *script = Cow::Owned(local_script);
1404 },
1405 Err(why) => warn!("Could not restore script from file {:?}", why),
1406 }
1407}
1408
1409#[derive(Clone, Copy)]
1410enum ExternalScriptKind {
1411 Deferred,
1412 ParsingBlocking,
1413 AsapInOrder,
1414 Asap,
1415}