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 base::id::WebViewId;
13use dom_struct::dom_struct;
14use encoding_rs::Encoding;
15use html5ever::{LocalName, Prefix, local_name};
16use js::context::JSContext;
17use js::rust::{HandleObject, Stencil};
18use net_traits::http_status::HttpStatus;
19use net_traits::request::{
20 CorsSettings, CredentialsMode, Destination, ParserMetadata, RequestBuilder, RequestId,
21};
22use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
23use servo_url::ServoUrl;
24use style::attr::AttrValue;
25use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
26use stylo_atoms::Atom;
27use uuid::Uuid;
28
29use crate::document_loader::LoadType;
30use crate::dom::attr::Attr;
31use crate::dom::bindings::cell::DomRefCell;
32use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
33use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
34use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
35use crate::dom::bindings::codegen::UnionTypes::{
36 TrustedScriptOrString, TrustedScriptURLOrUSVString,
37};
38use crate::dom::bindings::error::{Error, Fallible};
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::refcounted::Trusted;
41use crate::dom::bindings::reflector::DomGlobal;
42use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
43use crate::dom::bindings::str::DOMString;
44use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
45use crate::dom::document::Document;
46use crate::dom::element::{
47 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
48 referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
49 set_cross_origin_attribute,
50};
51use crate::dom::event::{Event, EventBubbles, EventCancelable};
52use crate::dom::global_scope_script_execution::{ClassicScript, ErrorReporting, RethrowErrors};
53use crate::dom::globalscope::GlobalScope;
54use crate::dom::html::htmlelement::HTMLElement;
55use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits};
56use crate::dom::performance::performanceresourcetiming::InitiatorType;
57use crate::dom::trustedtypes::trustedscript::TrustedScript;
58use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
59use crate::dom::virtualmethods::VirtualMethods;
60use crate::dom::window::Window;
61use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
62use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
63use crate::script_module::{
64 ImportMap, ModuleOwner, ModuleTree, ScriptFetchOptions, fetch_an_external_module_script,
65 fetch_inline_module_script, parse_an_import_map_string, register_import_map,
66};
67use crate::script_runtime::{CanGc, IntroductionType};
68
69#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq, MallocSizeOf)]
71pub(crate) struct ScriptId(#[no_trace] Uuid);
72
73#[dom_struct]
74pub(crate) struct HTMLScriptElement {
75 htmlelement: HTMLElement,
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 #[ignore_malloc_size_of = "Defined in uuid"]
101 id: ScriptId,
102
103 script_text: DomRefCell<DOMString>,
105
106 from_an_external_file: Cell<bool>,
108
109 #[no_trace]
112 introduction_type_override: Cell<Option<&'static CStr>>,
113}
114
115impl HTMLScriptElement {
116 fn new_inherited(
117 local_name: LocalName,
118 prefix: Option<Prefix>,
119 document: &Document,
120 creator: ElementCreator,
121 ) -> HTMLScriptElement {
122 HTMLScriptElement {
123 id: ScriptId(Uuid::new_v4()),
124 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
125 already_started: Cell::new(false),
126 parser_inserted: Cell::new(creator.is_parser_created()),
127 non_blocking: Cell::new(!creator.is_parser_created()),
128 parser_document: Dom::from_ref(document),
129 preparation_time_document: MutNullableDom::new(None),
130 line_number: creator.return_line_number(),
131 script_text: DomRefCell::new(DOMString::new()),
132 from_an_external_file: Cell::new(false),
133 introduction_type_override: Cell::new(None),
134 }
135 }
136
137 pub(crate) fn new(
138 local_name: LocalName,
139 prefix: Option<Prefix>,
140 document: &Document,
141 proto: Option<HandleObject>,
142 creator: ElementCreator,
143 can_gc: CanGc,
144 ) -> DomRoot<HTMLScriptElement> {
145 Node::reflect_node_with_proto(
146 Box::new(HTMLScriptElement::new_inherited(
147 local_name, prefix, document, creator,
148 )),
149 document,
150 proto,
151 can_gc,
152 )
153 }
154
155 pub(crate) fn get_script_id(&self) -> ScriptId {
156 self.id
157 }
158}
159
160pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
163 "application/ecmascript",
164 "application/javascript",
165 "application/x-ecmascript",
166 "application/x-javascript",
167 "text/ecmascript",
168 "text/javascript",
169 "text/javascript1.0",
170 "text/javascript1.1",
171 "text/javascript1.2",
172 "text/javascript1.3",
173 "text/javascript1.4",
174 "text/javascript1.5",
175 "text/jscript",
176 "text/livescript",
177 "text/x-ecmascript",
178 "text/x-javascript",
179];
180
181#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
182pub(crate) enum ScriptType {
183 Classic,
184 Module,
185 ImportMap,
186}
187
188#[derive(JSTraceable, MallocSizeOf)]
189pub(crate) struct CompiledSourceCode {
190 #[ignore_malloc_size_of = "SM handles JS values"]
191 pub(crate) source_code: Stencil,
192 #[conditional_malloc_size_of = "Rc is hard"]
193 pub(crate) original_text: Rc<DOMString>,
194}
195
196#[derive(JSTraceable, MallocSizeOf)]
197pub(crate) enum SourceCode {
198 Text(#[conditional_malloc_size_of] Rc<DOMString>),
199 Compiled(CompiledSourceCode),
200}
201
202#[derive(JSTraceable, MallocSizeOf)]
203pub(crate) struct ScriptOrigin {
204 pub code: SourceCode,
205 #[no_trace]
206 pub url: ServoUrl,
207 external: bool,
208 pub fetch_options: ScriptFetchOptions,
209 type_: ScriptType,
210 unminified_dir: Option<String>,
211 import_map: Fallible<ImportMap>,
212}
213
214impl ScriptOrigin {
215 pub(crate) fn internal(
216 text: Rc<DOMString>,
217 url: ServoUrl,
218 fetch_options: ScriptFetchOptions,
219 type_: ScriptType,
220 unminified_dir: Option<String>,
221 import_map: Fallible<ImportMap>,
222 ) -> ScriptOrigin {
223 ScriptOrigin {
224 code: SourceCode::Text(text),
225 url,
226 external: false,
227 fetch_options,
228 type_,
229 unminified_dir,
230 import_map,
231 }
232 }
233
234 pub(crate) fn external(
235 text: Rc<DOMString>,
236 url: ServoUrl,
237 fetch_options: ScriptFetchOptions,
238 type_: ScriptType,
239 unminified_dir: Option<String>,
240 ) -> ScriptOrigin {
241 ScriptOrigin {
242 code: SourceCode::Text(text),
243 url,
244 external: true,
245 fetch_options,
246 type_,
247 unminified_dir,
248 import_map: Err(Error::NotFound(None)),
249 }
250 }
251
252 pub(crate) fn text(&self) -> Rc<DOMString> {
253 match &self.code {
254 SourceCode::Text(text) => Rc::clone(text),
255 SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text),
256 }
257 }
258}
259
260fn finish_fetching_a_classic_script(
262 elem: &HTMLScriptElement,
263 script_kind: ExternalScriptKind,
264 url: ServoUrl,
265 load: ScriptResult,
266 cx: &mut js::context::JSContext,
267) {
268 let document;
271
272 match script_kind {
273 ExternalScriptKind::Asap => {
274 document = elem.preparation_time_document.get().unwrap();
275 document.asap_script_loaded(elem, load, CanGc::from_cx(cx))
276 },
277 ExternalScriptKind::AsapInOrder => {
278 document = elem.preparation_time_document.get().unwrap();
279 document.asap_in_order_script_loaded(elem, load, CanGc::from_cx(cx))
280 },
281 ExternalScriptKind::Deferred => {
282 document = elem.parser_document.as_rooted();
283 document.deferred_script_loaded(elem, load, CanGc::from_cx(cx));
284 },
285 ExternalScriptKind::ParsingBlocking => {
286 document = elem.parser_document.as_rooted();
287 document.pending_parsing_blocking_script_loaded(elem, load, cx);
288 },
289 }
290
291 document.finish_load(LoadType::Script(url), cx);
292}
293
294pub(crate) type ScriptResult = Result<Script, ()>;
295
296#[derive(JSTraceable, MallocSizeOf)]
298#[expect(clippy::large_enum_variant)]
299pub(crate) enum Script {
300 Classic(ClassicScript),
301 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
302 ImportMap(ScriptOrigin),
303}
304
305struct ClassicContext {
307 elem: Trusted<HTMLScriptElement>,
309 kind: ExternalScriptKind,
311 character_encoding: &'static Encoding,
314 data: Vec<u8>,
316 metadata: Option<Metadata>,
318 url: ServoUrl,
320 status: Result<(), NetworkError>,
322 fetch_options: ScriptFetchOptions,
324 response_was_cors_cross_origin: bool,
326}
327
328impl FetchResponseListener for ClassicContext {
329 fn process_request_body(&mut self, _: RequestId) {}
331
332 fn process_request_eof(&mut self, _: RequestId) {}
334
335 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
336 self.metadata = metadata.ok().map(|meta| {
337 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
338 match meta {
339 FetchMetadata::Unfiltered(m) => m,
340 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
341 }
342 });
343
344 let status = self
345 .metadata
346 .as_ref()
347 .map(|m| m.status.clone())
348 .unwrap_or_else(HttpStatus::new_error);
349
350 self.status = {
351 if status.is_error() {
352 Err(NetworkError::ResourceLoadError(
353 "No http status code received".to_owned(),
354 ))
355 } else if status.is_success() {
356 Ok(())
357 } else {
358 Err(NetworkError::ResourceLoadError(format!(
359 "HTTP error code {}",
360 status.code()
361 )))
362 }
363 };
364 }
365
366 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
367 if self.status.is_ok() {
368 self.data.append(&mut chunk);
369 }
370 }
371
372 fn process_response_eof(
375 mut self,
376 cx: &mut js::context::JSContext,
377 _: RequestId,
378 response: Result<(), NetworkError>,
379 timing: ResourceFetchTiming,
380 ) {
381 match (response.as_ref(), self.status.as_ref()) {
382 (Err(error), _) | (_, Err(error)) => {
383 error!("Fetching classic script failed {:?}", error);
384 finish_fetching_a_classic_script(
386 &self.elem.root(),
387 self.kind,
388 self.url.clone(),
389 Err(()),
390 cx,
391 );
392
393 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
395 return;
396 },
397 _ => {},
398 };
399
400 let metadata = self.metadata.take().unwrap();
401 let final_url = metadata.final_url;
402
403 let encoding = metadata
406 .charset
407 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
408 .unwrap_or(self.character_encoding);
409
410 let (mut source_text, _, _) = encoding.decode(&self.data);
412
413 let elem = self.elem.root();
414 let global = elem.global();
415
416 if let Some(window) = global.downcast::<Window>() {
417 substitute_with_local_script(window, &mut source_text, final_url.clone());
418 }
419
420 let muted_errors = self.response_was_cors_cross_origin;
422
423 let script = global.create_a_classic_script(
426 source_text,
427 final_url.clone(),
428 self.fetch_options.clone(),
429 ErrorReporting::from(muted_errors),
430 Some(IntroductionType::SRC_SCRIPT),
431 1,
432 true,
433 );
434
435 let load = Script::Classic(script);
466 finish_fetching_a_classic_script(&elem, self.kind, self.url.clone(), Ok(load), cx);
467 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
470 }
471
472 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
473 let global = &self.resource_timing_global();
474 let elem = self.elem.root();
475 global.report_csp_violations(violations, Some(elem.upcast()), None);
476 }
477}
478
479impl ResourceTimingListener for ClassicContext {
480 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
481 let initiator_type = InitiatorType::LocalName(
482 self.elem
483 .root()
484 .upcast::<Element>()
485 .local_name()
486 .to_string(),
487 );
488 (initiator_type, self.url.clone())
489 }
490
491 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
492 self.elem.root().owner_document().global()
493 }
494}
495
496#[allow(clippy::too_many_arguments)]
499pub(crate) fn script_fetch_request(
500 webview_id: WebViewId,
501 url: ServoUrl,
502 cors_setting: Option<CorsSettings>,
503 options: ScriptFetchOptions,
504) -> RequestBuilder {
505 create_a_potential_cors_request(
508 Some(webview_id),
509 url,
510 Destination::Script,
511 cors_setting,
512 None,
513 options.referrer,
514 )
515 .parser_metadata(options.parser_metadata)
516 .integrity_metadata(options.integrity_metadata.clone())
517 .referrer_policy(options.referrer_policy)
518 .cryptographic_nonce_metadata(options.cryptographic_nonce)
519}
520
521fn fetch_a_classic_script(
523 script: &HTMLScriptElement,
524 kind: ExternalScriptKind,
525 url: ServoUrl,
526 cors_setting: Option<CorsSettings>,
527 options: ScriptFetchOptions,
528 character_encoding: &'static Encoding,
529) {
530 let doc = script.owner_document();
532 let global = script.global();
533 let request =
534 script_fetch_request(doc.webview_id(), url.clone(), cors_setting, options.clone())
535 .with_global_scope(&global);
536
537 let context = ClassicContext {
540 elem: Trusted::new(script),
541 kind,
542 character_encoding,
543 data: vec![],
544 metadata: None,
545 url: url.clone(),
546 status: Ok(()),
547 fetch_options: options,
548 response_was_cors_cross_origin: false,
549 };
550 doc.fetch(LoadType::Script(url), request, context);
551}
552
553impl HTMLScriptElement {
554 pub(crate) fn set_initial_script_text(&self) {
556 *self.script_text.borrow_mut() = self.text();
557 }
558
559 fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> {
561 if self.script_text.borrow().clone() != self.text() {
565 *self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string(
566 &self.owner_global(),
567 self.Text(),
568 "HTMLScriptElement text",
569 can_gc,
570 )?;
571 }
572
573 Ok(())
574 }
575
576 pub(crate) fn prepare(&self, introduction_type_override: Option<&'static CStr>, can_gc: CanGc) {
578 self.introduction_type_override
579 .set(introduction_type_override);
580
581 if self.already_started.get() {
583 return;
584 }
585
586 let was_parser_inserted = self.parser_inserted.get();
591 self.parser_inserted.set(false);
592
593 let element = self.upcast::<Element>();
596 let asynch = element.has_attribute(&local_name!("async"));
597 if was_parser_inserted && !asynch {
599 self.non_blocking.set(true);
600 }
601
602 if self.prepare_the_script_text(can_gc).is_err() {
605 return;
606 }
607 let text = self.script_text.borrow().clone();
609 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
611 return;
612 }
613
614 if !self.upcast::<Node>().is_connected() {
616 return;
617 }
618
619 let script_type = if let Some(ty) = self.get_script_type() {
620 ty
622 } else {
623 return;
625 };
626
627 if was_parser_inserted {
631 self.parser_inserted.set(true);
632 self.non_blocking.set(false);
633 }
634
635 self.already_started.set(true);
637
638 let doc = self.owner_document();
640 self.preparation_time_document.set(Some(&doc));
641
642 if self.parser_inserted.get() && *self.parser_document != *doc {
646 return;
647 }
648
649 if !doc.scripting_enabled() {
651 return;
652 }
653
654 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
656 return;
657 }
658
659 let global = &doc.global();
660
661 if !element.has_attribute(&local_name!("src")) &&
663 global
664 .get_csp_list()
665 .should_elements_inline_type_behavior_be_blocked(
666 global,
667 element,
668 InlineCheckType::Script,
669 &text.str(),
670 self.line_number as u32,
671 )
672 {
673 warn!("Blocking inline script due to CSP");
674 return;
675 }
676
677 if script_type == ScriptType::Classic {
679 let for_attribute = element.get_attribute(&local_name!("for"));
680 let event_attribute = element.get_attribute(&local_name!("event"));
681 if let (Some(ref for_attribute), Some(ref event_attribute)) =
682 (for_attribute, event_attribute)
683 {
684 let for_value = for_attribute.value().to_ascii_lowercase();
685 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
686 if for_value != "window" {
687 return;
688 }
689
690 let event_value = event_attribute.value().to_ascii_lowercase();
691 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
692 if event_value != "onload" && event_value != "onload()" {
693 return;
694 }
695 }
696 }
697
698 let encoding = element
703 .get_attribute(&local_name!("charset"))
704 .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
705 .unwrap_or_else(|| doc.encoding());
706
707 let cors_setting = cors_setting_for_element(element);
709
710 let module_credentials_mode = match script_type {
712 ScriptType::Classic => CredentialsMode::CredentialsSameOrigin,
713 ScriptType::Module | ScriptType::ImportMap => reflect_cross_origin_attribute(element)
714 .map_or(
715 CredentialsMode::CredentialsSameOrigin,
716 |attr| match &*attr.str() {
717 "use-credentials" => CredentialsMode::Include,
718 "anonymous" => CredentialsMode::CredentialsSameOrigin,
719 _ => CredentialsMode::CredentialsSameOrigin,
720 },
721 ),
722 };
723
724 let el = self.upcast::<Element>();
729 let cryptographic_nonce = if el.is_nonceable() || !el.has_attribute(&local_name!("nonce")) {
730 el.nonce_value().trim().to_owned()
731 } else {
732 String::new()
733 };
734
735 let im_attribute = element.get_attribute(&local_name!("integrity"));
738 let integrity_val = im_attribute.as_ref().map(|a| a.value());
739 let integrity_metadata = match integrity_val {
740 Some(ref value) => &***value,
741 None => "",
742 };
743
744 let referrer_policy = referrer_policy_for_element(self.upcast::<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: integrity_metadata.to_owned(),
761 parser_metadata,
762 referrer: self.global().get_referrer(),
763 referrer_policy,
764 credentials_mode: module_credentials_mode,
765 };
766
767 let base_url = doc.base_url();
772 if let Some(src) = element.get_attribute(&local_name!("src")) {
773 if script_type == ScriptType::ImportMap {
777 self.queue_error_event();
780 return;
781 }
782
783 let src = src.value();
785
786 if src.is_empty() {
788 self.queue_error_event();
789 return;
790 }
791
792 self.from_an_external_file.set(true);
794
795 let url = match base_url.join(&src) {
797 Ok(url) => url,
798 Err(_) => {
799 warn!("error parsing URL for script {}", &**src);
800 self.queue_error_event();
801 return;
802 },
803 };
804
805 match script_type {
812 ScriptType::Classic => {
813 let kind = if element.has_attribute(&local_name!("defer")) &&
814 was_parser_inserted &&
815 !asynch
816 {
817 ExternalScriptKind::Deferred
819 } else if was_parser_inserted && !asynch {
820 ExternalScriptKind::ParsingBlocking
822 } else if !asynch && !self.non_blocking.get() {
823 ExternalScriptKind::AsapInOrder
825 } else {
826 ExternalScriptKind::Asap
828 };
829
830 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
832
833 match kind {
835 ExternalScriptKind::Deferred => doc.add_deferred_script(self),
836 ExternalScriptKind::ParsingBlocking => {
837 doc.set_pending_parsing_blocking_script(self, None)
838 },
839 ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
840 ExternalScriptKind::Asap => doc.add_asap_script(self),
841 }
842 },
843 ScriptType::Module => {
844 if integrity_val.is_none() {
847 options.integrity_metadata = global
848 .import_map()
849 .resolve_a_module_integrity_metadata(&url);
850 }
851
852 fetch_an_external_module_script(
854 url.clone(),
855 ModuleOwner::Window(Trusted::new(self)),
856 options,
857 can_gc,
858 );
859
860 if !asynch && was_parser_inserted {
861 doc.add_deferred_script(self);
863 } else if !asynch && !self.non_blocking.get() {
864 doc.push_asap_in_order_script(self);
866 } else {
867 doc.add_asap_script(self);
869 };
870 },
871 ScriptType::ImportMap => (),
872 }
873 } else {
874 assert!(!text.is_empty());
877
878 let text_rc = Rc::new(text.clone());
879
880 match script_type {
882 ScriptType::Classic => {
883 let script = self.global().create_a_classic_script(
886 std::borrow::Cow::Borrowed(&text.str()),
887 base_url,
888 options,
889 ErrorReporting::Unmuted,
890 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT)),
891 self.line_number as u32,
892 false,
893 );
894 let result = Ok(Script::Classic(script));
895
896 if was_parser_inserted &&
897 doc.get_current_parser()
898 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
899 doc.get_script_blocking_stylesheets_count() > 0
900 {
901 doc.set_pending_parsing_blocking_script(self, Some(result));
903 } else {
904 self.execute(result, can_gc);
906 }
907 },
908 ScriptType::Module => {
909 if !asynch && was_parser_inserted {
913 doc.add_deferred_script(self);
914 } else if !asynch && !self.non_blocking.get() {
915 doc.push_asap_in_order_script(self);
916 } else {
917 doc.add_asap_script(self);
918 };
919
920 fetch_inline_module_script(
921 ModuleOwner::Window(Trusted::new(self)),
922 text_rc,
923 base_url.clone(),
924 options,
925 self.line_number as u32,
926 can_gc,
927 );
928 },
929 ScriptType::ImportMap => {
930 let import_map_result = parse_an_import_map_string(
933 ModuleOwner::Window(Trusted::new(self)),
934 Rc::clone(&text_rc),
935 base_url.clone(),
936 );
937 let script = Script::ImportMap(ScriptOrigin::internal(
938 text_rc,
939 base_url,
940 options,
941 script_type,
942 self.global().unminified_js_dir(),
943 import_map_result,
944 ));
945
946 self.execute(Ok(script), can_gc);
948 },
949 }
950 }
951 }
952
953 pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) {
955 let doc = self.owner_document();
957
958 if *doc != *self.preparation_time_document.get().unwrap() {
960 return;
961 }
962
963 let script = match result {
965 Err(_) => {
967 self.dispatch_error_event(can_gc);
968 return;
969 },
970
971 Ok(script) => script,
972 };
973
974 let neutralized_doc =
978 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
979 let doc = self.owner_document();
980 doc.incr_ignore_destructive_writes_counter();
981 Some(doc)
982 } else {
983 None
984 };
985
986 let document = self.owner_document();
987
988 match script {
989 Script::Classic(script) => {
990 let old_script = document.GetCurrentScript();
992
993 if self.upcast::<Node>().is_in_a_shadow_tree() {
996 document.set_current_script(None)
997 } else {
998 document.set_current_script(Some(self))
999 }
1000
1001 _ = self.owner_window().as_global_scope().run_a_classic_script(
1003 script,
1004 RethrowErrors::No,
1005 can_gc,
1006 );
1007
1008 document.set_current_script(old_script.as_deref());
1010 },
1011 Script::Module(module_tree) => {
1012 document.set_current_script(None);
1014
1015 self.owner_window().as_global_scope().run_a_module_script(
1017 module_tree,
1018 false,
1019 can_gc,
1020 );
1021 },
1022 Script::ImportMap(script) => {
1023 register_import_map(&self.owner_global(), script.import_map, can_gc);
1025 },
1026 }
1027
1028 if let Some(doc) = neutralized_doc {
1031 doc.decr_ignore_destructive_writes_counter();
1032 }
1033
1034 if self.from_an_external_file.get() {
1036 self.dispatch_load_event(can_gc);
1037 }
1038 }
1039
1040 pub(crate) fn queue_error_event(&self) {
1041 self.owner_global()
1042 .task_manager()
1043 .dom_manipulation_task_source()
1044 .queue_simple_event(self.upcast(), atom!("error"));
1045 }
1046
1047 pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) {
1048 self.dispatch_event(
1049 atom!("load"),
1050 EventBubbles::DoesNotBubble,
1051 EventCancelable::NotCancelable,
1052 can_gc,
1053 );
1054 }
1055
1056 pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) {
1057 self.dispatch_event(
1058 atom!("error"),
1059 EventBubbles::DoesNotBubble,
1060 EventCancelable::NotCancelable,
1061 can_gc,
1062 );
1063 }
1064
1065 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1067 let element = self.upcast::<Element>();
1068
1069 let type_attr = element.get_attribute(&local_name!("type"));
1070 let language_attr = element.get_attribute(&local_name!("language"));
1071
1072 match (
1073 type_attr.as_ref().map(|t| t.value()),
1074 language_attr.as_ref().map(|l| l.value()),
1075 ) {
1076 (Some(ref ty), _) if ty.is_empty() => {
1077 debug!("script type empty, inferring js");
1078 Some(ScriptType::Classic)
1079 },
1080 (None, Some(ref 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(ref lang)) => {
1089 debug!("script language={}", &***lang);
1090 let language = format!("text/{}", &***lang);
1091
1092 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1093 Some(ScriptType::Classic)
1094 } else {
1095 None
1096 }
1097 },
1098 (Some(ref ty), _) => {
1099 debug!("script type={}", &***ty);
1100
1101 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1102 return Some(ScriptType::Module);
1103 }
1104
1105 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1106 return Some(ScriptType::ImportMap);
1107 }
1108
1109 if SCRIPT_JS_MIMES
1110 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1111 {
1112 Some(ScriptType::Classic)
1113 } else {
1114 None
1115 }
1116 },
1117 }
1118 }
1119
1120 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1121 self.parser_inserted.set(parser_inserted);
1122 }
1123
1124 pub(crate) fn get_parser_inserted(&self) -> bool {
1125 self.parser_inserted.get()
1126 }
1127
1128 pub(crate) fn set_already_started(&self, already_started: bool) {
1129 self.already_started.set(already_started);
1130 }
1131
1132 pub(crate) fn get_non_blocking(&self) -> bool {
1133 self.non_blocking.get()
1134 }
1135
1136 fn dispatch_event(
1137 &self,
1138 type_: Atom,
1139 bubbles: EventBubbles,
1140 cancelable: EventCancelable,
1141 can_gc: CanGc,
1142 ) -> bool {
1143 let window = self.owner_window();
1144 let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc);
1145 event.fire(self.upcast(), can_gc)
1146 }
1147
1148 fn text(&self) -> DOMString {
1149 match self.Text() {
1150 TrustedScriptOrString::String(value) => value,
1151 TrustedScriptOrString::TrustedScript(trusted_script) => {
1152 DOMString::from(trusted_script.to_string())
1153 },
1154 }
1155 }
1156}
1157
1158impl VirtualMethods for HTMLScriptElement {
1159 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1160 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1161 }
1162
1163 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1164 self.super_type()
1165 .unwrap()
1166 .attribute_mutated(attr, mutation, can_gc);
1167 if *attr.local_name() == local_name!("src") {
1168 if let AttributeMutation::Set(..) = mutation {
1169 if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1170 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1171 }
1172 }
1173 }
1174 }
1175
1176 fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
1178 if let Some(s) = self.super_type() {
1179 s.children_changed(mutation, can_gc);
1180 }
1181
1182 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1183 let script = DomRoot::from_ref(self);
1184 self.owner_document().add_delayed_task(
1188 task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| {
1189 script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1190 }),
1191 );
1192 }
1193 }
1194
1195 fn post_connection_steps(&self, cx: &mut JSContext) {
1197 if let Some(s) = self.super_type() {
1198 s.post_connection_steps(cx);
1199 }
1200
1201 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1202 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::from_cx(cx));
1203 }
1204 }
1205
1206 fn cloning_steps(
1207 &self,
1208 cx: &mut JSContext,
1209 copy: &Node,
1210 maybe_doc: Option<&Document>,
1211 clone_children: CloneChildrenFlag,
1212 ) {
1213 if let Some(s) = self.super_type() {
1214 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1215 }
1216
1217 if self.already_started.get() {
1219 copy.downcast::<HTMLScriptElement>()
1220 .unwrap()
1221 .set_already_started(true);
1222 }
1223 }
1224}
1225
1226impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1227 fn Src(&self) -> TrustedScriptURLOrUSVString {
1229 let element = self.upcast::<Element>();
1230 element.get_trusted_type_url_attribute(&local_name!("src"))
1231 }
1232
1233 fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> {
1235 let element = self.upcast::<Element>();
1236 let local_name = &local_name!("src");
1237 let value = TrustedScriptURL::get_trusted_script_url_compliant_string(
1238 &element.owner_global(),
1239 value,
1240 "HTMLScriptElement",
1241 local_name,
1242 can_gc,
1243 )?;
1244 element.set_attribute(
1245 local_name,
1246 AttrValue::String(value.str().to_owned()),
1247 can_gc,
1248 );
1249 Ok(())
1250 }
1251
1252 make_getter!(Type, "type");
1254 make_setter!(SetType, "type");
1256
1257 make_getter!(Charset, "charset");
1259 make_setter!(SetCharset, "charset");
1261
1262 fn Async(&self) -> bool {
1264 self.non_blocking.get() ||
1265 self.upcast::<Element>()
1266 .has_attribute(&local_name!("async"))
1267 }
1268
1269 fn SetAsync(&self, value: bool, can_gc: CanGc) {
1271 self.non_blocking.set(false);
1272 self.upcast::<Element>()
1273 .set_bool_attribute(&local_name!("async"), value, can_gc);
1274 }
1275
1276 make_bool_getter!(Defer, "defer");
1278 make_bool_setter!(SetDefer, "defer");
1280
1281 make_bool_getter!(NoModule, "nomodule");
1283 make_bool_setter!(SetNoModule, "nomodule");
1285
1286 make_getter!(Integrity, "integrity");
1288 make_setter!(SetIntegrity, "integrity");
1290
1291 make_getter!(Event, "event");
1293 make_setter!(SetEvent, "event");
1295
1296 make_getter!(HtmlFor, "for");
1298 make_setter!(SetHtmlFor, "for");
1300
1301 fn GetCrossOrigin(&self) -> Option<DOMString> {
1303 reflect_cross_origin_attribute(self.upcast::<Element>())
1304 }
1305
1306 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1308 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1309 }
1310
1311 fn ReferrerPolicy(&self) -> DOMString {
1313 reflect_referrer_policy_attribute(self.upcast::<Element>())
1314 }
1315
1316 make_setter!(SetReferrerPolicy, "referrerpolicy");
1318
1319 fn InnerText(&self) -> TrustedScriptOrString {
1321 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1323 }
1324
1325 fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1327 let value = TrustedScript::get_trusted_script_compliant_string(
1330 &self.owner_global(),
1331 input,
1332 "HTMLScriptElement innerText",
1333 can_gc,
1334 )?;
1335 *self.script_text.borrow_mut() = value.clone();
1336 self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1338 Ok(())
1339 }
1340
1341 fn Text(&self) -> TrustedScriptOrString {
1343 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1344 }
1345
1346 fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1348 let value = TrustedScript::get_trusted_script_compliant_string(
1351 &self.owner_global(),
1352 value,
1353 "HTMLScriptElement text",
1354 can_gc,
1355 )?;
1356 *self.script_text.borrow_mut() = value.clone();
1358 Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1360 Ok(())
1361 }
1362
1363 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1365 Some(TrustedScriptOrString::String(
1367 self.upcast::<Node>().GetTextContent()?,
1368 ))
1369 }
1370
1371 fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1373 let value = TrustedScript::get_trusted_script_compliant_string(
1376 &self.owner_global(),
1377 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1378 "HTMLScriptElement textContent",
1379 can_gc,
1380 )?;
1381 *self.script_text.borrow_mut() = value.clone();
1383 self.upcast::<Node>()
1385 .set_text_content_for_element(Some(value), can_gc);
1386 Ok(())
1387 }
1388
1389 fn Supports(_window: &Window, type_: DOMString) -> bool {
1391 matches!(&*type_.str(), "classic" | "module" | "importmap")
1394 }
1395}
1396
1397pub(crate) fn substitute_with_local_script(
1398 window: &Window,
1399 script: &mut Cow<'_, str>,
1400 url: ServoUrl,
1401) {
1402 if window.local_script_source().is_none() {
1403 return;
1404 }
1405 let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1406 path = path.join(&url[url::Position::BeforeHost..]);
1407 debug!("Attempting to read script stored at: {:?}", path);
1408 match read_to_string(path.clone()) {
1409 Ok(local_script) => {
1410 debug!("Found script stored at: {:?}", path);
1411 *script = Cow::Owned(local_script);
1412 },
1413 Err(why) => warn!("Could not restore script from file {:?}", why),
1414 }
1415}
1416
1417#[derive(Clone, Copy)]
1418enum ExternalScriptKind {
1419 Deferred,
1420 ParsingBlocking,
1421 AsapInOrder,
1422 Asap,
1423}