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, ns};
16use js::jsval::UndefinedValue;
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::settings_stack::AutoEntryScript;
44use crate::dom::bindings::str::DOMString;
45use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
46use crate::dom::document::Document;
47use crate::dom::element::{
48 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
49 referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
50 set_cross_origin_attribute,
51};
52use crate::dom::event::{Event, EventBubbles, EventCancelable};
53use crate::dom::globalscope::{ClassicScript, ErrorReporting, GlobalScope, RethrowErrors};
54use crate::dom::html::htmlelement::HTMLElement;
55use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits};
56use crate::dom::performance::performanceresourcetiming::InitiatorType;
57use crate::dom::trustedscript::TrustedScript;
58use crate::dom::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 can_gc: CanGc,
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, can_gc)
276 },
277 ExternalScriptKind::AsapInOrder => {
278 document = elem.preparation_time_document.get().unwrap();
279 document.asap_in_order_script_loaded(elem, load, can_gc)
280 },
281 ExternalScriptKind::Deferred => {
282 document = elem.parser_document.as_rooted();
283 document.deferred_script_loaded(elem, load, can_gc);
284 },
285 ExternalScriptKind::ParsingBlocking => {
286 document = elem.parser_document.as_rooted();
287 document.pending_parsing_blocking_script_loaded(elem, load, can_gc);
288 },
289 }
290
291 document.finish_load(LoadType::Script(url), can_gc);
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 _: RequestId,
377 response: Result<(), NetworkError>,
378 timing: ResourceFetchTiming,
379 ) {
380 match (response.as_ref(), self.status.as_ref()) {
381 (Err(error), _) | (_, Err(error)) => {
382 error!("Fetching classic script failed {:?}", error);
383 finish_fetching_a_classic_script(
385 &self.elem.root(),
386 self.kind,
387 self.url.clone(),
388 Err(()),
389 CanGc::note(),
390 );
391
392 network_listener::submit_timing(&self, &response, &timing, CanGc::note());
394 return;
395 },
396 _ => {},
397 };
398
399 let metadata = self.metadata.take().unwrap();
400 let final_url = metadata.final_url;
401
402 let encoding = metadata
405 .charset
406 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
407 .unwrap_or(self.character_encoding);
408
409 let (mut source_text, _, _) = encoding.decode(&self.data);
411
412 let elem = self.elem.root();
413 let global = elem.global();
414
415 if let Some(window) = global.downcast::<Window>() {
416 substitute_with_local_script(window, &mut source_text, final_url.clone());
417 }
418
419 let muted_errors = self.response_was_cors_cross_origin;
421
422 let script = global.create_a_classic_script(
425 source_text,
426 final_url.clone(),
427 self.fetch_options.clone(),
428 ErrorReporting::from(muted_errors),
429 Some(IntroductionType::SRC_SCRIPT),
430 1,
431 true,
432 );
433
434 let load = Script::Classic(script);
465 finish_fetching_a_classic_script(
466 &elem,
467 self.kind,
468 self.url.clone(),
469 Ok(load),
470 CanGc::note(),
471 );
472 network_listener::submit_timing(&self, &response, &timing, CanGc::note());
475 }
476
477 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
478 let global = &self.resource_timing_global();
479 let elem = self.elem.root();
480 let source_position = elem
481 .upcast::<Element>()
482 .compute_source_position(elem.line_number as u32);
483 global.report_csp_violations(violations, Some(elem.upcast()), Some(source_position));
484 }
485}
486
487impl ResourceTimingListener for ClassicContext {
488 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
489 let initiator_type = InitiatorType::LocalName(
490 self.elem
491 .root()
492 .upcast::<Element>()
493 .local_name()
494 .to_string(),
495 );
496 (initiator_type, self.url.clone())
497 }
498
499 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
500 self.elem.root().owner_document().global()
501 }
502}
503
504#[allow(clippy::too_many_arguments)]
507pub(crate) fn script_fetch_request(
508 webview_id: WebViewId,
509 url: ServoUrl,
510 cors_setting: Option<CorsSettings>,
511 options: ScriptFetchOptions,
512) -> RequestBuilder {
513 create_a_potential_cors_request(
516 Some(webview_id),
517 url,
518 Destination::Script,
519 cors_setting,
520 None,
521 options.referrer,
522 )
523 .parser_metadata(options.parser_metadata)
524 .integrity_metadata(options.integrity_metadata.clone())
525 .referrer_policy(options.referrer_policy)
526 .cryptographic_nonce_metadata(options.cryptographic_nonce)
527}
528
529fn fetch_a_classic_script(
531 script: &HTMLScriptElement,
532 kind: ExternalScriptKind,
533 url: ServoUrl,
534 cors_setting: Option<CorsSettings>,
535 options: ScriptFetchOptions,
536 character_encoding: &'static Encoding,
537) {
538 let doc = script.owner_document();
540 let global = script.global();
541 let request =
542 script_fetch_request(doc.webview_id(), url.clone(), cors_setting, options.clone())
543 .with_global_scope(&global);
544
545 let context = ClassicContext {
548 elem: Trusted::new(script),
549 kind,
550 character_encoding,
551 data: vec![],
552 metadata: None,
553 url: url.clone(),
554 status: Ok(()),
555 fetch_options: options,
556 response_was_cors_cross_origin: false,
557 };
558 doc.fetch(LoadType::Script(url), request, context);
559}
560
561impl HTMLScriptElement {
562 pub(crate) fn set_initial_script_text(&self) {
564 *self.script_text.borrow_mut() = self.text();
565 }
566
567 fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> {
569 if self.script_text.borrow().clone() != self.text() {
573 *self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string(
574 &self.owner_global(),
575 self.Text(),
576 "HTMLScriptElement text",
577 can_gc,
578 )?;
579 }
580
581 Ok(())
582 }
583
584 pub(crate) fn prepare(&self, introduction_type_override: Option<&'static CStr>, can_gc: CanGc) {
586 self.introduction_type_override
587 .set(introduction_type_override);
588
589 if self.already_started.get() {
591 return;
592 }
593
594 let was_parser_inserted = self.parser_inserted.get();
599 self.parser_inserted.set(false);
600
601 let element = self.upcast::<Element>();
604 let asynch = element.has_attribute(&local_name!("async"));
605 if was_parser_inserted && !asynch {
607 self.non_blocking.set(true);
608 }
609
610 if self.prepare_the_script_text(can_gc).is_err() {
613 return;
614 }
615 let text = self.script_text.borrow().clone();
617 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
619 return;
620 }
621
622 if !self.upcast::<Node>().is_connected() {
624 return;
625 }
626
627 let script_type = if let Some(ty) = self.get_script_type() {
628 ty
630 } else {
631 return;
633 };
634
635 if was_parser_inserted {
639 self.parser_inserted.set(true);
640 self.non_blocking.set(false);
641 }
642
643 self.already_started.set(true);
645
646 let doc = self.owner_document();
648 self.preparation_time_document.set(Some(&doc));
649
650 if self.parser_inserted.get() && *self.parser_document != *doc {
654 return;
655 }
656
657 if !doc.scripting_enabled() {
659 return;
660 }
661
662 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
664 return;
665 }
666
667 let global = &doc.global();
668
669 if !element.has_attribute(&local_name!("src")) &&
671 global
672 .get_csp_list()
673 .should_elements_inline_type_behavior_be_blocked(
674 global,
675 element,
676 InlineCheckType::Script,
677 &text.str(),
678 self.line_number as u32,
679 )
680 {
681 warn!("Blocking inline script due to CSP");
682 return;
683 }
684
685 if script_type == ScriptType::Classic {
687 let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
688 let event_attribute = element.get_attribute(&ns!(), &local_name!("event"));
689 if let (Some(ref for_attribute), Some(ref event_attribute)) =
690 (for_attribute, event_attribute)
691 {
692 let for_value = for_attribute.value().to_ascii_lowercase();
693 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
694 if for_value != "window" {
695 return;
696 }
697
698 let event_value = event_attribute.value().to_ascii_lowercase();
699 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
700 if event_value != "onload" && event_value != "onload()" {
701 return;
702 }
703 }
704 }
705
706 let encoding = element
711 .get_attribute(&ns!(), &local_name!("charset"))
712 .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
713 .unwrap_or_else(|| doc.encoding());
714
715 let cors_setting = cors_setting_for_element(element);
717
718 let module_credentials_mode = match script_type {
720 ScriptType::Classic => CredentialsMode::CredentialsSameOrigin,
721 ScriptType::Module | ScriptType::ImportMap => reflect_cross_origin_attribute(element)
722 .map_or(
723 CredentialsMode::CredentialsSameOrigin,
724 |attr| match &*attr.str() {
725 "use-credentials" => CredentialsMode::Include,
726 "anonymous" => CredentialsMode::CredentialsSameOrigin,
727 _ => CredentialsMode::CredentialsSameOrigin,
728 },
729 ),
730 };
731
732 let cryptographic_nonce = self.upcast::<Element>().nonce_value();
734
735 let im_attribute = element.get_attribute(&ns!(), &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 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(&ns!(), &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 fetch_an_external_module_script(
846 url.clone(),
847 ModuleOwner::Window(Trusted::new(self)),
848 options,
849 can_gc,
850 );
851
852 if !asynch && was_parser_inserted {
853 doc.add_deferred_script(self);
855 } else if !asynch && !self.non_blocking.get() {
856 doc.push_asap_in_order_script(self);
858 } else {
859 doc.add_asap_script(self);
861 };
862 },
863 ScriptType::ImportMap => (),
864 }
865 } else {
866 assert!(!text.is_empty());
869
870 let text_rc = Rc::new(text.clone());
871
872 match script_type {
874 ScriptType::Classic => {
875 let script = self.global().create_a_classic_script(
878 std::borrow::Cow::Borrowed(&text.str()),
879 base_url,
880 options,
881 ErrorReporting::Unmuted,
882 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT)),
883 self.line_number as u32,
884 false,
885 );
886 let result = Ok(Script::Classic(script));
887
888 if was_parser_inserted &&
889 doc.get_current_parser()
890 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
891 doc.get_script_blocking_stylesheets_count() > 0
892 {
893 doc.set_pending_parsing_blocking_script(self, Some(result));
895 } else {
896 self.execute(result, can_gc);
898 }
899 },
900 ScriptType::Module => {
901 if !asynch && was_parser_inserted {
905 doc.add_deferred_script(self);
906 } else if !asynch && !self.non_blocking.get() {
907 doc.push_asap_in_order_script(self);
908 } else {
909 doc.add_asap_script(self);
910 };
911
912 fetch_inline_module_script(
913 ModuleOwner::Window(Trusted::new(self)),
914 text_rc,
915 base_url.clone(),
916 options,
917 self.line_number as u32,
918 can_gc,
919 );
920 },
921 ScriptType::ImportMap => {
922 let import_map_result = parse_an_import_map_string(
925 ModuleOwner::Window(Trusted::new(self)),
926 Rc::clone(&text_rc),
927 base_url.clone(),
928 can_gc,
929 );
930 let script = Script::ImportMap(ScriptOrigin::internal(
931 text_rc,
932 base_url,
933 options,
934 script_type,
935 self.global().unminified_js_dir(),
936 import_map_result,
937 ));
938
939 self.execute(Ok(script), can_gc);
941 },
942 }
943 }
944 }
945
946 pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) {
948 let doc = self.owner_document();
950
951 if *doc != *self.preparation_time_document.get().unwrap() {
953 return;
954 }
955
956 let script = match result {
958 Err(_) => {
960 self.dispatch_error_event(can_gc);
961 return;
962 },
963
964 Ok(script) => script,
965 };
966
967 let neutralized_doc =
971 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
972 let doc = self.owner_document();
973 doc.incr_ignore_destructive_writes_counter();
974 Some(doc)
975 } else {
976 None
977 };
978
979 let document = self.owner_document();
980
981 match script {
982 Script::Classic(script) => {
983 let old_script = document.GetCurrentScript();
985
986 if self.upcast::<Node>().is_in_a_shadow_tree() {
989 document.set_current_script(None)
990 } else {
991 document.set_current_script(Some(self))
992 }
993
994 _ = self.owner_window().as_global_scope().run_a_classic_script(
996 script,
997 RethrowErrors::No,
998 can_gc,
999 );
1000
1001 document.set_current_script(old_script.as_deref());
1003 },
1004 Script::Module(module_tree) => {
1005 document.set_current_script(None);
1007
1008 self.run_a_module_script(module_tree, false, can_gc);
1010 },
1011 Script::ImportMap(script) => {
1012 register_import_map(&self.owner_global(), script.import_map, can_gc);
1014 },
1015 }
1016
1017 if let Some(doc) = neutralized_doc {
1020 doc.decr_ignore_destructive_writes_counter();
1021 }
1022
1023 if self.from_an_external_file.get() {
1025 self.dispatch_load_event(can_gc);
1026 }
1027 }
1028
1029 pub(crate) fn run_a_module_script(
1031 &self,
1032 module_tree: Rc<ModuleTree>,
1033 _rethrow_errors: bool,
1034 can_gc: CanGc,
1035 ) {
1036 let document = self.owner_document();
1039 if !document.is_fully_active() || !document.scripting_enabled() {
1040 return;
1041 }
1042
1043 let window = self.owner_window();
1045 let global = window.as_global_scope();
1046 let _aes = AutoEntryScript::new(global);
1047
1048 {
1050 let module_error = module_tree.get_rethrow_error().borrow();
1051 if module_error.is_some() {
1052 module_tree.report_error(global, can_gc);
1053 return;
1054 }
1055 }
1056
1057 let record = module_tree.get_record().map(|record| record.handle());
1058
1059 if let Some(record) = record {
1060 rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue());
1061 let evaluated = module_tree.execute_module(global, record, rval.handle_mut(), can_gc);
1062
1063 if let Err(exception) = evaluated {
1064 module_tree.set_rethrow_error(exception);
1065 module_tree.report_error(global, can_gc);
1066 }
1067 }
1068 }
1069
1070 pub(crate) fn queue_error_event(&self) {
1071 self.owner_global()
1072 .task_manager()
1073 .dom_manipulation_task_source()
1074 .queue_simple_event(self.upcast(), atom!("error"));
1075 }
1076
1077 pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) {
1078 self.dispatch_event(
1079 atom!("load"),
1080 EventBubbles::DoesNotBubble,
1081 EventCancelable::NotCancelable,
1082 can_gc,
1083 );
1084 }
1085
1086 pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) {
1087 self.dispatch_event(
1088 atom!("error"),
1089 EventBubbles::DoesNotBubble,
1090 EventCancelable::NotCancelable,
1091 can_gc,
1092 );
1093 }
1094
1095 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1097 let element = self.upcast::<Element>();
1098
1099 let type_attr = element.get_attribute(&ns!(), &local_name!("type"));
1100 let language_attr = element.get_attribute(&ns!(), &local_name!("language"));
1101
1102 match (
1103 type_attr.as_ref().map(|t| t.value()),
1104 language_attr.as_ref().map(|l| l.value()),
1105 ) {
1106 (Some(ref ty), _) if ty.is_empty() => {
1107 debug!("script type empty, inferring js");
1108 Some(ScriptType::Classic)
1109 },
1110 (None, Some(ref lang)) if lang.is_empty() => {
1111 debug!("script type empty, inferring js");
1112 Some(ScriptType::Classic)
1113 },
1114 (None, None) => {
1115 debug!("script type empty, inferring js");
1116 Some(ScriptType::Classic)
1117 },
1118 (None, Some(ref lang)) => {
1119 debug!("script language={}", &***lang);
1120 let language = format!("text/{}", &***lang);
1121
1122 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1123 Some(ScriptType::Classic)
1124 } else {
1125 None
1126 }
1127 },
1128 (Some(ref ty), _) => {
1129 debug!("script type={}", &***ty);
1130
1131 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1132 return Some(ScriptType::Module);
1133 }
1134
1135 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1136 return Some(ScriptType::ImportMap);
1137 }
1138
1139 if SCRIPT_JS_MIMES
1140 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1141 {
1142 Some(ScriptType::Classic)
1143 } else {
1144 None
1145 }
1146 },
1147 }
1148 }
1149
1150 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1151 self.parser_inserted.set(parser_inserted);
1152 }
1153
1154 pub(crate) fn get_parser_inserted(&self) -> bool {
1155 self.parser_inserted.get()
1156 }
1157
1158 pub(crate) fn set_already_started(&self, already_started: bool) {
1159 self.already_started.set(already_started);
1160 }
1161
1162 pub(crate) fn get_non_blocking(&self) -> bool {
1163 self.non_blocking.get()
1164 }
1165
1166 fn dispatch_event(
1167 &self,
1168 type_: Atom,
1169 bubbles: EventBubbles,
1170 cancelable: EventCancelable,
1171 can_gc: CanGc,
1172 ) -> bool {
1173 let window = self.owner_window();
1174 let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc);
1175 event.fire(self.upcast(), can_gc)
1176 }
1177
1178 fn text(&self) -> DOMString {
1179 match self.Text() {
1180 TrustedScriptOrString::String(value) => value,
1181 TrustedScriptOrString::TrustedScript(trusted_script) => {
1182 DOMString::from(trusted_script.to_string())
1183 },
1184 }
1185 }
1186}
1187
1188impl VirtualMethods for HTMLScriptElement {
1189 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1190 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1191 }
1192
1193 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1194 self.super_type()
1195 .unwrap()
1196 .attribute_mutated(attr, mutation, can_gc);
1197 if *attr.local_name() == local_name!("src") {
1198 if let AttributeMutation::Set(..) = mutation {
1199 if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1200 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1201 }
1202 }
1203 }
1204 }
1205
1206 fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
1208 if let Some(s) = self.super_type() {
1209 s.children_changed(mutation, can_gc);
1210 }
1211
1212 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1213 let script = DomRoot::from_ref(self);
1214 self.owner_document().add_delayed_task(
1218 task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| {
1219 script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1220 }),
1221 );
1222 }
1223 }
1224
1225 fn post_connection_steps(&self, can_gc: CanGc) {
1227 if let Some(s) = self.super_type() {
1228 s.post_connection_steps(can_gc);
1229 }
1230
1231 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1232 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1233 }
1234 }
1235
1236 fn cloning_steps(
1237 &self,
1238 copy: &Node,
1239 maybe_doc: Option<&Document>,
1240 clone_children: CloneChildrenFlag,
1241 can_gc: CanGc,
1242 ) {
1243 if let Some(s) = self.super_type() {
1244 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
1245 }
1246
1247 if self.already_started.get() {
1249 copy.downcast::<HTMLScriptElement>()
1250 .unwrap()
1251 .set_already_started(true);
1252 }
1253 }
1254}
1255
1256impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1257 fn Src(&self) -> TrustedScriptURLOrUSVString {
1259 let element = self.upcast::<Element>();
1260 element.get_trusted_type_url_attribute(&local_name!("src"))
1261 }
1262
1263 fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> {
1265 let element = self.upcast::<Element>();
1266 let local_name = &local_name!("src");
1267 let value = TrustedScriptURL::get_trusted_script_url_compliant_string(
1268 &element.owner_global(),
1269 value,
1270 "HTMLScriptElement",
1271 local_name,
1272 can_gc,
1273 )?;
1274 element.set_attribute(
1275 local_name,
1276 AttrValue::String(value.str().to_owned()),
1277 can_gc,
1278 );
1279 Ok(())
1280 }
1281
1282 make_getter!(Type, "type");
1284 make_setter!(SetType, "type");
1286
1287 make_getter!(Charset, "charset");
1289 make_setter!(SetCharset, "charset");
1291
1292 fn Async(&self) -> bool {
1294 self.non_blocking.get() ||
1295 self.upcast::<Element>()
1296 .has_attribute(&local_name!("async"))
1297 }
1298
1299 fn SetAsync(&self, value: bool, can_gc: CanGc) {
1301 self.non_blocking.set(false);
1302 self.upcast::<Element>()
1303 .set_bool_attribute(&local_name!("async"), value, can_gc);
1304 }
1305
1306 make_bool_getter!(Defer, "defer");
1308 make_bool_setter!(SetDefer, "defer");
1310
1311 make_bool_getter!(NoModule, "nomodule");
1313 make_bool_setter!(SetNoModule, "nomodule");
1315
1316 make_getter!(Integrity, "integrity");
1318 make_setter!(SetIntegrity, "integrity");
1320
1321 make_getter!(Event, "event");
1323 make_setter!(SetEvent, "event");
1325
1326 make_getter!(HtmlFor, "for");
1328 make_setter!(SetHtmlFor, "for");
1330
1331 fn GetCrossOrigin(&self) -> Option<DOMString> {
1333 reflect_cross_origin_attribute(self.upcast::<Element>())
1334 }
1335
1336 fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1338 set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1339 }
1340
1341 fn ReferrerPolicy(&self) -> DOMString {
1343 reflect_referrer_policy_attribute(self.upcast::<Element>())
1344 }
1345
1346 make_setter!(SetReferrerPolicy, "referrerpolicy");
1348
1349 fn InnerText(&self) -> TrustedScriptOrString {
1351 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1353 }
1354
1355 fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1357 let value = TrustedScript::get_trusted_script_compliant_string(
1360 &self.owner_global(),
1361 input,
1362 "HTMLScriptElement innerText",
1363 can_gc,
1364 )?;
1365 *self.script_text.borrow_mut() = value.clone();
1366 self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1368 Ok(())
1369 }
1370
1371 fn Text(&self) -> TrustedScriptOrString {
1373 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1374 }
1375
1376 fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1378 let value = TrustedScript::get_trusted_script_compliant_string(
1381 &self.owner_global(),
1382 value,
1383 "HTMLScriptElement text",
1384 can_gc,
1385 )?;
1386 *self.script_text.borrow_mut() = value.clone();
1388 Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1390 Ok(())
1391 }
1392
1393 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1395 Some(TrustedScriptOrString::String(
1397 self.upcast::<Node>().GetTextContent()?,
1398 ))
1399 }
1400
1401 fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1403 let value = TrustedScript::get_trusted_script_compliant_string(
1406 &self.owner_global(),
1407 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1408 "HTMLScriptElement textContent",
1409 can_gc,
1410 )?;
1411 *self.script_text.borrow_mut() = value.clone();
1413 self.upcast::<Node>()
1415 .set_text_content_for_element(Some(value), can_gc);
1416 Ok(())
1417 }
1418
1419 fn Supports(_window: &Window, type_: DOMString) -> bool {
1421 matches!(&*type_.str(), "classic" | "module" | "importmap")
1424 }
1425}
1426
1427pub(crate) fn substitute_with_local_script(
1428 window: &Window,
1429 script: &mut Cow<'_, str>,
1430 url: ServoUrl,
1431) {
1432 if window.local_script_source().is_none() {
1433 return;
1434 }
1435 let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1436 path = path.join(&url[url::Position::BeforeHost..]);
1437 debug!("Attempting to read script stored at: {:?}", path);
1438 match read_to_string(path.clone()) {
1439 Ok(local_script) => {
1440 debug!("Found script stored at: {:?}", path);
1441 *script = Cow::Owned(local_script);
1442 },
1443 Err(why) => warn!("Could not restore script from file {:?}", why),
1444 }
1445}
1446
1447#[derive(Clone, Copy)]
1448enum ExternalScriptKind {
1449 Deferred,
1450 ParsingBlocking,
1451 AsapInOrder,
1452 Asap,
1453}