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, 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::script_execution::ScriptOptions;
60use crate::dom::trustedtypes::trustedscript::TrustedScript;
61use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
62use crate::dom::window::Window;
63use crate::event_loop::document_loader::{LoadBlocker, LoadType};
64use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request_with_claim};
65use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
66use crate::modules::import_map::{ImportMap, parse_an_import_map_string, register_import_map};
67use crate::modules::script_module::{
68 ModuleTree, ScriptFetchOptions, fetch_an_external_module_script, fetch_inline_module_script,
69};
70use crate::runtime::script_runtime::IntroductionType;
71use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
72
73#[dom_struct]
74pub(crate) struct HTMLScriptElement {
75 htmlelement: HTMLElement,
76
77 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
79
80 already_started: Cell<bool>,
82
83 parser_inserted: Cell<bool>,
85
86 non_blocking: Cell<bool>,
90
91 parser_document: Dom<Document>,
94
95 preparation_time_document: MutNullableDom<Document>,
98
99 line_number: u64,
101
102 script_text: DomRefCell<DOMString>,
104
105 from_an_external_file: Cell<bool>,
107
108 blocking: MutNullableDom<DOMTokenList>,
110
111 marked_as_render_blocking: Cell<bool>,
114
115 result: DomRefCell<Option<ScriptResult>>,
117}
118
119impl HTMLScriptElement {
120 fn new_inherited(
121 local_name: LocalName,
122 prefix: Option<Prefix>,
123 document: &Document,
124 creator: ElementCreator,
125 ) -> HTMLScriptElement {
126 HTMLScriptElement {
127 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
128 already_started: Cell::new(false),
129 delaying_the_load_event: Default::default(),
130 parser_inserted: Cell::new(creator.is_parser_created()),
131 non_blocking: Cell::new(!creator.is_parser_created()),
132 parser_document: Dom::from_ref(document),
133 preparation_time_document: MutNullableDom::new(None),
134 line_number: creator.return_line_number(),
135 script_text: DomRefCell::new(DOMString::new()),
136 from_an_external_file: Cell::new(false),
137 blocking: Default::default(),
138 marked_as_render_blocking: Default::default(),
139 result: DomRefCell::new(None),
140 }
141 }
142
143 pub(crate) fn new(
144 cx: &mut js::context::JSContext,
145 local_name: LocalName,
146 prefix: Option<Prefix>,
147 document: &Document,
148 proto: Option<HandleObject>,
149 creator: ElementCreator,
150 ) -> DomRoot<HTMLScriptElement> {
151 Node::reflect_node_with_proto(
152 cx,
153 Box::new(HTMLScriptElement::new_inherited(
154 local_name, prefix, document, creator,
155 )),
156 document,
157 proto,
158 )
159 }
160
161 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
166 debug_assert!(self.delaying_the_load_event.borrow().is_none());
167
168 *self.delaying_the_load_event.borrow_mut() =
169 Some(LoadBlocker::new(document, LoadType::Script(url)));
170 }
171
172 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
179 let element = self.upcast::<Element>();
180
181 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
182 ExternalScriptKind::Asap
183 } else if !self.parser_inserted.get() {
184 ExternalScriptKind::AsapInOrder
185 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
186 {
187 ExternalScriptKind::Deferred
188 } else {
189 ExternalScriptKind::ParsingBlocking
190 }
191 }
192
193 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
195 match script_kind {
196 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
197 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
198 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
199 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
200 }
201 }
202}
203
204pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
207 "application/ecmascript",
208 "application/javascript",
209 "application/x-ecmascript",
210 "application/x-javascript",
211 "text/ecmascript",
212 "text/javascript",
213 "text/javascript1.0",
214 "text/javascript1.1",
215 "text/javascript1.2",
216 "text/javascript1.3",
217 "text/javascript1.4",
218 "text/javascript1.5",
219 "text/jscript",
220 "text/livescript",
221 "text/x-ecmascript",
222 "text/x-javascript",
223];
224
225#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
226pub(crate) enum ScriptType {
227 Classic,
228 Module,
229 ImportMap,
230}
231
232fn finish_fetching_a_script(
234 elem: &HTMLScriptElement,
235 script_kind: ExternalScriptKind,
236 cx: &mut JSContext,
237) {
238 let load = elem.result.take().expect("Result must be ready to proceed");
239
240 match script_kind {
242 ExternalScriptKind::Asap => {
243 let document = elem.preparation_time_document.get().unwrap();
244 document.asap_script_loaded(cx, elem, load)
245 },
246 ExternalScriptKind::AsapInOrder => {
247 let document = elem.preparation_time_document.get().unwrap();
248 document.asap_in_order_script_loaded(cx, elem, load)
249 },
250 ExternalScriptKind::Deferred => {
251 let document = elem.parser_document.as_rooted();
252 document.deferred_script_loaded(cx, elem, load);
253 },
254 ExternalScriptKind::ParsingBlocking => {
255 let document = elem.parser_document.as_rooted();
256 document.pending_parsing_blocking_script_loaded(elem, load, cx);
257 },
258 }
259
260 LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
262}
263
264pub(crate) type ScriptResult = Result<Script, ()>;
265
266#[derive(JSTraceable, MallocSizeOf)]
268pub(crate) enum Script {
269 Classic(ClassicScript),
270 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
271 ImportMap(Fallible<ImportMap>),
272}
273
274struct ClassicContext {
276 elem: Trusted<HTMLScriptElement>,
278 kind: ExternalScriptKind,
280 character_encoding: &'static Encoding,
283 data: Vec<u8>,
285 metadata: Option<Metadata>,
287 url: UrlWithBlobClaim,
289 status: Result<(), NetworkError>,
291 fetch_options: ScriptFetchOptions,
293 response_was_cors_cross_origin: bool,
295}
296
297impl FetchResponseListener for ClassicContext {
298 fn process_request_body(&mut self, _: RequestId) {}
300
301 fn process_response(
302 &mut self,
303 _: &mut js::context::JSContext,
304 _: RequestId,
305 metadata: Result<FetchMetadata, NetworkError>,
306 ) {
307 self.metadata = metadata.ok().map(|meta| {
308 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
309 match meta {
310 FetchMetadata::Unfiltered(m) => m,
311 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
312 }
313 });
314
315 let status = self
316 .metadata
317 .as_ref()
318 .map(|m| m.status.clone())
319 .unwrap_or_else(HttpStatus::new_error);
320
321 self.status = {
322 if status.is_error() {
323 Err(NetworkError::ResourceLoadError(
324 "No http status code received".to_owned(),
325 ))
326 } else if status.is_success() {
327 Ok(())
328 } else {
329 Err(NetworkError::ResourceLoadError(format!(
330 "HTTP error code {}",
331 status.code()
332 )))
333 }
334 };
335 }
336
337 fn process_response_chunk(
338 &mut self,
339 _: &mut js::context::JSContext,
340 _: RequestId,
341 mut chunk: Vec<u8>,
342 ) {
343 if self.status.is_ok() {
344 self.data.append(&mut chunk);
345 }
346 }
347
348 fn process_response_eof(
351 mut self,
352 cx: &mut js::context::JSContext,
353 _: RequestId,
354 response: Result<(), NetworkError>,
355 timing: ResourceFetchTiming,
356 ) {
357 network_listener::submit_timing(cx, &self, &response, &timing);
359
360 let elem = self.elem.root();
361
362 match (response.as_ref(), self.status.as_ref()) {
363 (Err(error), _) | (_, Err(error)) => {
364 error!(
365 "Fetching classic script failed {:?} ({:?})",
366 error, self.url
367 );
368 *elem.result.borrow_mut() = Some(Err(()));
370 finish_fetching_a_script(&elem, self.kind, cx);
371 return;
372 },
373 _ => {},
374 };
375
376 let metadata = self.metadata.take().unwrap();
377 let final_url = metadata.final_url;
378
379 let encoding = metadata
382 .charset
383 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
384 .unwrap_or(self.character_encoding);
385
386 let (mut source_text, _, _) = encoding.decode(&self.data);
388
389 let global = elem.global();
390
391 if let Some(window) = global.downcast::<Window>() &&
392 let Some(script_source) = window.local_script_source()
393 {
394 substitute_with_local_script(script_source, &mut source_text, &final_url);
395 }
396
397 let mut script_options = ScriptOptions::External;
399 script_options.set(
400 ScriptOptions::MutedErrors,
401 self.response_was_cors_cross_origin,
402 );
403
404 let script = global.create_a_classic_script(
407 cx,
408 source_text,
409 final_url,
410 script_options,
411 self.fetch_options.clone(),
412 Some(IntroductionType::SRC_SCRIPT),
413 1,
414 );
415
416 *elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
447 finish_fetching_a_script(&elem, self.kind, cx);
448 }
450
451 fn process_csp_violations(
452 &mut self,
453 cx: &mut js::context::JSContext,
454 _request_id: RequestId,
455 violations: Vec<Violation>,
456 ) {
457 let global = &self.resource_timing_global();
458 let elem = self.elem.root();
459 global.report_csp_violations(cx, violations, Some(elem.upcast()), None);
460 }
461
462 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
463 self.data.reserve(size - self.data.len());
464 }
465}
466
467impl ResourceTimingListener for ClassicContext {
468 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
469 let initiator_type = InitiatorType::LocalName(
470 self.elem
471 .root()
472 .upcast::<Element>()
473 .local_name()
474 .to_string(),
475 );
476 (initiator_type, self.url.url())
477 }
478
479 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
480 self.elem.root().owner_document().global()
481 }
482}
483
484#[allow(clippy::too_many_arguments)]
487pub(crate) fn script_fetch_request(
488 webview_id: WebViewId,
489 url: UrlWithBlobClaim,
490 cors_setting: Option<CorsSettings>,
491 options: ScriptFetchOptions,
492 referrer: Referrer,
493) -> RequestBuilder {
494 create_a_potential_cors_request_with_claim(
497 Some(webview_id),
498 url,
499 Destination::Script,
500 cors_setting,
501 None,
502 referrer,
503 )
504 .parser_metadata(options.parser_metadata)
505 .integrity_metadata(options.integrity_metadata.clone())
506 .referrer_policy(options.referrer_policy)
507 .cryptographic_nonce_metadata(options.cryptographic_nonce)
508}
509
510fn fetch_a_classic_script(
512 script: &HTMLScriptElement,
513 kind: ExternalScriptKind,
514 url: UrlWithBlobClaim,
515 cors_setting: Option<CorsSettings>,
516 options: ScriptFetchOptions,
517 character_encoding: &'static Encoding,
518) {
519 let doc = script.owner_document();
521 let global = script.global();
522 let referrer = global.get_referrer();
523 let request = script_fetch_request(
524 doc.webview_id(),
525 url.clone(),
526 cors_setting,
527 options.clone(),
528 referrer,
529 )
530 .with_global_scope(&global);
531
532 let context = ClassicContext {
535 elem: Trusted::new(script),
536 kind,
537 character_encoding,
538 data: vec![],
539 metadata: None,
540 url,
541 status: Ok(()),
542 fetch_options: options,
543 response_was_cors_cross_origin: false,
544 };
545 doc.fetch_background(request, context);
546}
547
548impl HTMLScriptElement {
549 pub(crate) fn set_initial_script_text(&self) {
551 *self.script_text.borrow_mut() = self.text();
552 }
553
554 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
556 if *self.script_text.borrow() != self.text() {
560 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
561 cx,
562 &self.owner_global(),
563 self.Text(),
564 "HTMLScriptElement text",
565 )?;
566 }
567
568 Ok(())
569 }
570
571 fn has_render_blocking_attribute(&self) -> bool {
572 self.blocking
573 .get()
574 .is_some_and(|list| list.Contains("render".into()))
575 }
576
577 fn potentially_render_blocking(&self) -> bool {
579 if self.has_render_blocking_attribute() {
583 return true;
584 }
585 let element = self.upcast::<Element>();
586 self.get_script_type()
590 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
591 self.parser_inserted.get() &&
592 !element.has_attribute(&local_name!("async")) &&
593 !element.has_attribute(&local_name!("defer"))
594 }
595
596 pub(crate) fn prepare(
598 &self,
599 cx: &mut JSContext,
600 introduction_type_override: Option<&'static CStr>,
601 ) {
602 let introduction_type =
603 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
604
605 if self.already_started.get() {
607 return;
608 }
609
610 let was_parser_inserted = self.parser_inserted.get();
615 self.parser_inserted.set(false);
616
617 let element = self.upcast::<Element>();
620 let asynch = element.has_attribute(&local_name!("async"));
621 if was_parser_inserted && !asynch {
623 self.non_blocking.set(true);
624 }
625
626 if self.prepare_the_script_text(cx).is_err() {
629 return;
630 }
631 let text: Cow<'_, str> = Cow::Owned(String::from(self.script_text.borrow().str()));
633 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
635 return;
636 }
637
638 if !self.upcast::<Node>().is_connected() {
640 return;
641 }
642
643 let script_type = if let Some(ty) = self.get_script_type() {
644 ty
646 } else {
647 return;
649 };
650
651 if was_parser_inserted {
655 self.parser_inserted.set(true);
656 self.non_blocking.set(false);
657 }
658
659 self.already_started.set(true);
661
662 let doc = self.owner_document();
664 self.preparation_time_document.set(Some(&doc));
665
666 if self.parser_inserted.get() && *self.parser_document != *doc {
670 return;
671 }
672
673 if !doc.scripting_enabled() {
675 return;
676 }
677
678 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
680 return;
681 }
682
683 let global = &doc.global();
684
685 if !element.has_attribute(&local_name!("src")) &&
687 global
688 .get_csp_list()
689 .should_elements_inline_type_behavior_be_blocked(
690 cx,
691 global,
692 element,
693 InlineCheckType::Script,
694 &text,
695 self.line_number as u32,
696 )
697 {
698 warn!("Blocking inline script due to CSP");
699 return;
700 }
701
702 if script_type == ScriptType::Classic {
704 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
705 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
706 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
707 let for_value = for_attribute.to_ascii_lowercase();
708 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
709 if for_value != "window" {
710 return;
711 }
712
713 let event_value = event_attribute.to_ascii_lowercase();
714 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
715 if event_value != "onload" && event_value != "onload()" {
716 return;
717 }
718 }
719 }
720
721 let encoding = element
726 .get_attribute_string_value(&local_name!("charset"))
727 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
728 .unwrap_or_else(|| doc.encoding());
729
730 let cors_setting = cors_setting_for_element(element);
732
733 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
735
736 let cryptographic_nonce =
741 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
742 element.nonce_value().trim().to_owned()
743 } else {
744 String::new()
745 };
746
747 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
750 let integrity_val_is_none = integrity_val.is_none();
751 let integrity_metadata = integrity_val.unwrap_or_default();
752
753 let referrer_policy = referrer_policy_for_element(element);
755
756 let parser_metadata = if self.parser_inserted.get() {
761 ParserMetadata::ParserInserted
762 } else {
763 ParserMetadata::NotParserInserted
764 };
765
766 let mut script_fetch_options = ScriptFetchOptions {
768 cryptographic_nonce,
769 integrity_metadata,
770 parser_metadata,
771 referrer_policy,
772 credentials_mode: module_credentials_mode,
773 render_blocking: false,
774 };
775
776 let base_url = doc.base_url();
779
780 let kind = self.get_script_kind(script_type);
781 let delayed_document = self.get_script_active_document(kind);
782
783 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
786 if script_type == ScriptType::ImportMap {
788 self.queue_error_event();
791 return;
792 }
793
794 if src.is_empty() {
796 self.queue_error_event();
797 return;
798 }
799
800 self.from_an_external_file.set(true);
802
803 let url = match base_url.join(&src) {
805 Ok(url) => url,
806 Err(_) => {
807 warn!("error parsing URL for script {}", src);
808 self.queue_error_event();
809 return;
810 },
811 };
812 let url = ensure_blob_referenced_by_url_is_kept_alive(global, url);
813
814 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
816 self.marked_as_render_blocking.set(true);
817 doc.increment_render_blocking_element_count();
818 }
819
820 self.delay_load_event(&delayed_document, url.url());
822
823 if self.marked_as_render_blocking.get() {
825 script_fetch_options.render_blocking = true;
826 }
827
828 match script_type {
830 ScriptType::Classic => {
831 fetch_a_classic_script(
833 self,
834 kind,
835 url,
836 cors_setting,
837 script_fetch_options,
838 encoding,
839 );
840 },
841 ScriptType::Module => {
842 if integrity_val_is_none {
845 script_fetch_options.integrity_metadata = global
846 .import_map()
847 .resolve_a_module_integrity_metadata(&url.url());
848 }
849
850 let script = DomRoot::from_ref(self);
851
852 fetch_an_external_module_script(
854 cx,
855 url,
856 global,
857 script_fetch_options,
858 move |cx, module_tree| {
859 let load = module_tree.map(Script::Module).ok_or(());
860 *script.result.borrow_mut() = Some(load);
861
862 finish_fetching_a_script(&script, kind, cx);
863 },
864 );
865 },
866 ScriptType::ImportMap => (),
867 }
868 } else {
869 assert!(!text.is_empty());
872
873 match script_type {
875 ScriptType::Classic => {
876 let script = self.global().create_a_classic_script(
879 cx,
880 text,
881 base_url,
882 ScriptOptions::empty(),
883 script_fetch_options,
884 introduction_type,
885 self.line_number as u32,
886 );
887 let result = Ok(Script::Classic(script));
888
889 if was_parser_inserted &&
890 doc.get_current_parser()
891 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
892 doc.has_a_stylesheet_that_is_blocking_scripts()
893 {
894 doc.set_pending_parsing_blocking_script(self, Some(result));
896 } else {
897 self.execute(cx, result);
899 }
900 return;
901 },
902 ScriptType::Module => {
903 self.delay_load_event(&delayed_document, base_url.clone());
905
906 if self.potentially_render_blocking() &&
908 doc.allows_adding_render_blocking_elements()
909 {
910 self.marked_as_render_blocking.set(true);
912 doc.increment_render_blocking_element_count();
913
914 script_fetch_options.render_blocking = true;
916 }
917
918 let script = DomRoot::from_ref(self);
919 fetch_inline_module_script(
922 cx,
923 global,
924 text,
925 base_url,
926 script_fetch_options,
927 self.line_number as u32,
928 introduction_type,
929 move |_, module_tree| {
930 let load = module_tree.map(Script::Module).ok_or(());
931 *script.result.borrow_mut() = Some(load);
932
933 let trusted = Trusted::new(&*script);
934
935 script
937 .owner_global()
938 .task_manager()
939 .networking_task_source()
940 .queue(task!(terminate_module_fetch: move |cx| {
941 finish_fetching_a_script(&trusted.root(), kind, cx);
943 }));
944 },
945 );
946 },
947 ScriptType::ImportMap => {
948 let import_map_result = parse_an_import_map_string(cx, global, &text, base_url);
951 let script = Script::ImportMap(import_map_result);
952
953 self.execute(cx, Ok(script));
955 return;
956 },
957 }
958 }
959
960 match kind {
962 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
963 ExternalScriptKind::ParsingBlocking => {
964 delayed_document.set_pending_parsing_blocking_script(self, None);
965 },
966 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
967 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
968 }
969 }
970
971 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
973 let doc = self.owner_document();
975
976 if *doc != *self.preparation_time_document.get().unwrap() {
978 return;
979 }
980
981 if self.marked_as_render_blocking.replace(false) {
983 self.marked_as_render_blocking.set(false);
984 doc.decrement_render_blocking_element_count();
985 }
986
987 let script = match result {
988 Err(_) => {
990 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
991 return;
992 },
993
994 Ok(script) => script,
995 };
996
997 let neutralized_doc =
1001 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
1002 let doc = self.owner_document();
1003 doc.incr_ignore_destructive_writes_counter();
1004 Some(doc)
1005 } else {
1006 None
1007 };
1008
1009 let document = self.owner_document();
1010
1011 match script {
1012 Script::Classic(script) => {
1013 let old_script = document.GetCurrentScript();
1015
1016 if self.upcast::<Node>().is_in_a_shadow_tree() {
1019 document.set_current_script(None)
1020 } else {
1021 document.set_current_script(Some(self))
1022 }
1023
1024 _ = self.owner_global().run_a_classic_script(
1026 cx,
1027 script,
1028 RethrowErrors::No,
1029 None, );
1031
1032 document.set_current_script(old_script.as_deref());
1034 },
1035 Script::Module(module_tree) => {
1036 document.set_current_script(None);
1038
1039 self.owner_global()
1041 .run_a_module_script(cx, module_tree, false);
1042 },
1043 Script::ImportMap(import_map) => {
1044 register_import_map(cx, &self.owner_global(), import_map);
1046 },
1047 }
1048
1049 if let Some(doc) = neutralized_doc {
1052 doc.decr_ignore_destructive_writes_counter();
1053 }
1054
1055 if self.from_an_external_file.get() {
1057 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1058 }
1059 }
1060
1061 pub(crate) fn queue_error_event(&self) {
1062 self.owner_global()
1063 .task_manager()
1064 .dom_manipulation_task_source()
1065 .queue_simple_event(self.upcast(), atom!("error"));
1066 }
1067
1068 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1070 let element = self.upcast::<Element>();
1071
1072 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1073 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1074
1075 match (type_attr, language_attr) {
1076 (Some(ty), _) if ty.is_empty() => {
1077 debug!("script type empty, inferring js");
1078 Some(ScriptType::Classic)
1079 },
1080 (None, Some(lang)) if lang.is_empty() => {
1081 debug!("script type empty, inferring js");
1082 Some(ScriptType::Classic)
1083 },
1084 (None, None) => {
1085 debug!("script type empty, inferring js");
1086 Some(ScriptType::Classic)
1087 },
1088 (None, Some(lang)) => {
1089 debug!("script language={}", lang);
1090 let language = format!("text/{}", lang);
1091
1092 if SCRIPT_JS_MIMES
1093 .iter()
1094 .any(|mime| language.eq_ignore_ascii_case(mime))
1095 {
1096 Some(ScriptType::Classic)
1097 } else {
1098 None
1099 }
1100 },
1101 (Some(ty), _) => {
1102 debug!("script type={}", ty);
1103
1104 if ty
1105 .trim_matches(HTML_SPACE_CHARACTERS)
1106 .eq_ignore_ascii_case("module")
1107 {
1108 return Some(ScriptType::Module);
1109 }
1110
1111 if ty
1112 .trim_matches(HTML_SPACE_CHARACTERS)
1113 .eq_ignore_ascii_case("importmap")
1114 {
1115 return Some(ScriptType::ImportMap);
1116 }
1117
1118 if SCRIPT_JS_MIMES.iter().any(|mime| {
1119 ty.trim_matches(HTML_SPACE_CHARACTERS)
1120 .eq_ignore_ascii_case(mime)
1121 }) {
1122 Some(ScriptType::Classic)
1123 } else {
1124 None
1125 }
1126 },
1127 }
1128 }
1129
1130 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1131 self.parser_inserted.set(parser_inserted);
1132 }
1133
1134 pub(crate) fn set_already_started(&self, already_started: bool) {
1135 self.already_started.set(already_started);
1136 }
1137
1138 fn text(&self) -> DOMString {
1139 match self.Text() {
1140 TrustedScriptOrString::String(value) => value,
1141 TrustedScriptOrString::TrustedScript(trusted_script) => {
1142 DOMString::from(trusted_script.to_string())
1143 },
1144 }
1145 }
1146}
1147
1148impl VirtualMethods for HTMLScriptElement {
1149 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1150 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1151 }
1152
1153 fn attribute_mutated(
1154 &self,
1155 cx: &mut js::context::JSContext,
1156 attr: AttrRef<'_>,
1157 mutation: AttributeMutation,
1158 ) {
1159 self.super_type()
1160 .unwrap()
1161 .attribute_mutated(cx, attr, mutation);
1162 if *attr.local_name() == local_name!("src") {
1163 if let AttributeMutation::Set(..) = mutation &&
1164 !self.parser_inserted.get() &&
1165 self.upcast::<Node>().is_connected()
1166 {
1167 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1168 }
1169 } else if *attr.local_name() == local_name!("blocking") &&
1170 !self.has_render_blocking_attribute() &&
1171 self.marked_as_render_blocking.replace(false)
1172 {
1173 let document = self.owner_document();
1174 document.decrement_render_blocking_element_count();
1175 }
1176 }
1177
1178 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1180 if let Some(s) = self.super_type() {
1181 s.children_changed(cx, mutation);
1182 }
1183
1184 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1185 let script = DomRoot::from_ref(self);
1186 self.owner_document().add_delayed_task(
1190 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1191 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1192 }),
1193 );
1194 }
1195 }
1196
1197 fn post_connection_steps(&self, cx: &mut JSContext) {
1199 if let Some(s) = self.super_type() {
1200 s.post_connection_steps(cx);
1201 }
1202
1203 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1204 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1205 }
1206 }
1207
1208 fn cloning_steps(
1209 &self,
1210 cx: &mut JSContext,
1211 copy: &Node,
1212 maybe_doc: Option<&Document>,
1213 clone_children: CloneChildrenFlag,
1214 ) {
1215 if let Some(s) = self.super_type() {
1216 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1217 }
1218
1219 if self.already_started.get() {
1221 copy.downcast::<HTMLScriptElement>()
1222 .unwrap()
1223 .set_already_started(true);
1224 }
1225 }
1226
1227 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1228 self.super_type().unwrap().unbind_from_tree(cx, context);
1229
1230 if self.marked_as_render_blocking.replace(false) {
1231 let document = self.owner_document();
1232 document.decrement_render_blocking_element_count();
1233 }
1234 }
1235}
1236
1237impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1238 fn Src(&self) -> TrustedScriptURLOrUSVString {
1240 let element = self.upcast::<Element>();
1241 element.get_trusted_type_url_attribute(&local_name!("src"))
1242 }
1243
1244 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1246 let element = self.upcast::<Element>();
1247 let local_name = &local_name!("src");
1248 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1249 cx,
1250 &element.owner_global(),
1251 value,
1252 &format!("HTMLScriptElement {}", local_name),
1253 )?;
1254 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1255 Ok(())
1256 }
1257
1258 make_getter!(Type, "type");
1260 make_setter!(SetType, "type");
1262
1263 make_getter!(Charset, "charset");
1265 make_setter!(SetCharset, "charset");
1267
1268 fn Async(&self) -> bool {
1270 self.non_blocking.get() ||
1271 self.upcast::<Element>()
1272 .has_attribute(&local_name!("async"))
1273 }
1274
1275 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1277 self.non_blocking.set(false);
1278 self.upcast::<Element>()
1279 .set_bool_attribute(cx, &local_name!("async"), value);
1280 }
1281
1282 make_bool_getter!(Defer, "defer");
1284 make_bool_setter!(SetDefer, "defer");
1286
1287 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1289 self.blocking.or_init(|| {
1290 DOMTokenList::new(
1291 cx,
1292 self.upcast(),
1293 &local_name!("blocking"),
1294 Some(vec![Atom::from("render")]),
1295 )
1296 })
1297 }
1298
1299 make_bool_getter!(NoModule, "nomodule");
1301 make_bool_setter!(SetNoModule, "nomodule");
1303
1304 make_getter!(Integrity, "integrity");
1306 make_setter!(SetIntegrity, "integrity");
1308
1309 make_getter!(Event, "event");
1311 make_setter!(SetEvent, "event");
1313
1314 make_getter!(HtmlFor, "for");
1316 make_setter!(SetHtmlFor, "for");
1318
1319 fn GetCrossOrigin(&self) -> Option<DOMString> {
1321 reflect_cross_origin_attribute(self.upcast::<Element>())
1322 }
1323
1324 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1326 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1327 }
1328
1329 fn ReferrerPolicy(&self) -> DOMString {
1331 reflect_referrer_policy_attribute(self.upcast::<Element>())
1332 }
1333
1334 make_setter!(SetReferrerPolicy, "referrerpolicy");
1336
1337 fn InnerText(&self) -> TrustedScriptOrString {
1339 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1341 }
1342
1343 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1345 let value = TrustedScript::get_trusted_type_compliant_string(
1348 cx,
1349 &self.owner_global(),
1350 input,
1351 "HTMLScriptElement innerText",
1352 )?;
1353 *self.script_text.borrow_mut() = value.clone();
1354 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1356 Ok(())
1357 }
1358
1359 fn Text(&self) -> TrustedScriptOrString {
1361 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1362 }
1363
1364 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1366 let value = TrustedScript::get_trusted_type_compliant_string(
1369 cx,
1370 &self.owner_global(),
1371 value,
1372 "HTMLScriptElement text",
1373 )?;
1374 *self.script_text.borrow_mut() = value.clone();
1376 Node::string_replace_all(cx, value, self.upcast::<Node>());
1378 Ok(())
1379 }
1380
1381 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1383 Some(TrustedScriptOrString::String(
1385 self.upcast::<Node>().GetTextContent()?,
1386 ))
1387 }
1388
1389 fn SetTextContent(
1391 &self,
1392 cx: &mut JSContext,
1393 value: Option<TrustedScriptOrString>,
1394 ) -> Fallible<()> {
1395 let value = TrustedScript::get_trusted_type_compliant_string(
1398 cx,
1399 &self.owner_global(),
1400 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1401 "HTMLScriptElement textContent",
1402 )?;
1403 *self.script_text.borrow_mut() = value.clone();
1405 self.upcast::<Node>()
1407 .set_text_content_for_element(cx, Some(value));
1408 Ok(())
1409 }
1410
1411 fn Supports(_window: &Window, type_: DOMString) -> bool {
1413 matches!(&*type_.str(), "classic" | "module" | "importmap")
1416 }
1417}
1418
1419pub fn substitute_with_local_script(
1420 script_source: &str,
1421 script: &mut Cow<'_, str>,
1422 url: &ServoUrl,
1423) {
1424 let mut path = PathBuf::from(script_source);
1425 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1426 debug!("Attempting to read script stored at: {:?}", path);
1427 match read_to_string(path.clone()) {
1428 Ok(local_script) => {
1429 debug!("Found script stored at: {:?}", path);
1430 *script = Cow::Owned(local_script);
1431 },
1432 Err(why) => warn!("Could not restore script from file {:?}", why),
1433 }
1434}
1435
1436#[derive(Clone, Copy)]
1437enum ExternalScriptKind {
1438 Deferred,
1439 ParsingBlocking,
1440 AsapInOrder,
1441 Asap,
1442}