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, Stencil};
17use net_traits::http_status::HttpStatus;
18use net_traits::request::{
19 CorsSettings, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId,
20};
21use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
22use script_bindings::cell::DomRefCell;
23use servo_base::id::WebViewId;
24use servo_url::ServoUrl;
25use style::attr::AttrValue;
26use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
27use stylo_atoms::Atom;
28use uuid::Uuid;
29
30use crate::document_loader::{LoadBlocker, LoadType};
31use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
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::domtokenlist::DOMTokenList;
47use crate::dom::element::attributes::storage::AttrRef;
48use crate::dom::element::{
49 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
50 cors_settings_attribute_credential_mode, referrer_policy_for_element,
51 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
52};
53use crate::dom::event::{Event, EventBubbles, EventCancelable};
54use crate::dom::global_scope_script_execution::{ClassicScript, ErrorReporting, RethrowErrors};
55use crate::dom::globalscope::GlobalScope;
56use crate::dom::html::htmlelement::HTMLElement;
57use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits, UnbindContext};
58use crate::dom::performance::performanceresourcetiming::InitiatorType;
59use crate::dom::trustedtypes::trustedscript::TrustedScript;
60use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
61use crate::dom::virtualmethods::VirtualMethods;
62use crate::dom::window::Window;
63use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
64use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
65use crate::script_module::{
66 ImportMap, ModuleTree, ScriptFetchOptions, fetch_an_external_module_script,
67 fetch_inline_module_script, parse_an_import_map_string, register_import_map,
68};
69use crate::script_runtime::{CanGc, IntroductionType};
70
71#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq, MallocSizeOf)]
73pub(crate) struct ScriptId(#[no_trace] Uuid);
74
75#[dom_struct]
76pub(crate) struct HTMLScriptElement {
77 htmlelement: HTMLElement,
78
79 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
81
82 already_started: Cell<bool>,
84
85 parser_inserted: Cell<bool>,
87
88 non_blocking: Cell<bool>,
92
93 parser_document: Dom<Document>,
96
97 preparation_time_document: MutNullableDom<Document>,
100
101 line_number: u64,
103
104 #[ignore_malloc_size_of = "Defined in uuid"]
106 id: ScriptId,
107
108 script_text: DomRefCell<DOMString>,
110
111 from_an_external_file: Cell<bool>,
113
114 blocking: MutNullableDom<DOMTokenList>,
116
117 marked_as_render_blocking: Cell<bool>,
120
121 result: DomRefCell<Option<ScriptResult>>,
123}
124
125impl HTMLScriptElement {
126 fn new_inherited(
127 local_name: LocalName,
128 prefix: Option<Prefix>,
129 document: &Document,
130 creator: ElementCreator,
131 ) -> HTMLScriptElement {
132 HTMLScriptElement {
133 id: ScriptId(Uuid::new_v4()),
134 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
135 already_started: Cell::new(false),
136 delaying_the_load_event: Default::default(),
137 parser_inserted: Cell::new(creator.is_parser_created()),
138 non_blocking: Cell::new(!creator.is_parser_created()),
139 parser_document: Dom::from_ref(document),
140 preparation_time_document: MutNullableDom::new(None),
141 line_number: creator.return_line_number(),
142 script_text: DomRefCell::new(DOMString::new()),
143 from_an_external_file: Cell::new(false),
144 blocking: Default::default(),
145 marked_as_render_blocking: Default::default(),
146 result: DomRefCell::new(None),
147 }
148 }
149
150 pub(crate) fn new(
151 cx: &mut js::context::JSContext,
152 local_name: LocalName,
153 prefix: Option<Prefix>,
154 document: &Document,
155 proto: Option<HandleObject>,
156 creator: ElementCreator,
157 ) -> DomRoot<HTMLScriptElement> {
158 Node::reflect_node_with_proto(
159 cx,
160 Box::new(HTMLScriptElement::new_inherited(
161 local_name, prefix, document, creator,
162 )),
163 document,
164 proto,
165 )
166 }
167
168 pub(crate) fn get_script_id(&self) -> ScriptId {
169 self.id
170 }
171
172 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
177 debug_assert!(self.delaying_the_load_event.borrow().is_none());
178
179 *self.delaying_the_load_event.borrow_mut() =
180 Some(LoadBlocker::new(document, LoadType::Script(url)));
181 }
182
183 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
190 let element = self.upcast::<Element>();
191
192 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
193 ExternalScriptKind::Asap
194 } else if !self.parser_inserted.get() {
195 ExternalScriptKind::AsapInOrder
196 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
197 {
198 ExternalScriptKind::Deferred
199 } else {
200 ExternalScriptKind::ParsingBlocking
201 }
202 }
203
204 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
206 match script_kind {
207 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
208 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
209 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
210 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
211 }
212 }
213}
214
215pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
218 "application/ecmascript",
219 "application/javascript",
220 "application/x-ecmascript",
221 "application/x-javascript",
222 "text/ecmascript",
223 "text/javascript",
224 "text/javascript1.0",
225 "text/javascript1.1",
226 "text/javascript1.2",
227 "text/javascript1.3",
228 "text/javascript1.4",
229 "text/javascript1.5",
230 "text/jscript",
231 "text/livescript",
232 "text/x-ecmascript",
233 "text/x-javascript",
234];
235
236#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
237pub(crate) enum ScriptType {
238 Classic,
239 Module,
240 ImportMap,
241}
242
243#[derive(JSTraceable, MallocSizeOf)]
244pub(crate) struct CompiledSourceCode {
245 #[ignore_malloc_size_of = "SM handles JS values"]
246 pub(crate) source_code: Stencil,
247 #[conditional_malloc_size_of = "Rc is hard"]
248 pub(crate) original_text: Rc<DOMString>,
249}
250
251#[derive(JSTraceable, MallocSizeOf)]
252pub(crate) enum SourceCode {
253 Text(#[conditional_malloc_size_of] Rc<DOMString>),
254 Compiled(CompiledSourceCode),
255}
256
257#[derive(JSTraceable, MallocSizeOf)]
258pub(crate) struct ScriptOrigin {
259 pub code: SourceCode,
260 #[no_trace]
261 pub url: ServoUrl,
262 external: bool,
263 pub fetch_options: ScriptFetchOptions,
264 type_: ScriptType,
265 unminified_dir: Option<String>,
266 import_map: Fallible<ImportMap>,
267}
268
269impl ScriptOrigin {
270 pub(crate) fn internal(
271 text: Rc<DOMString>,
272 url: ServoUrl,
273 fetch_options: ScriptFetchOptions,
274 type_: ScriptType,
275 unminified_dir: Option<String>,
276 import_map: Fallible<ImportMap>,
277 ) -> ScriptOrigin {
278 ScriptOrigin {
279 code: SourceCode::Text(text),
280 url,
281 external: false,
282 fetch_options,
283 type_,
284 unminified_dir,
285 import_map,
286 }
287 }
288
289 pub(crate) fn external(
290 text: Rc<DOMString>,
291 url: ServoUrl,
292 fetch_options: ScriptFetchOptions,
293 type_: ScriptType,
294 unminified_dir: Option<String>,
295 ) -> ScriptOrigin {
296 ScriptOrigin {
297 code: SourceCode::Text(text),
298 url,
299 external: true,
300 fetch_options,
301 type_,
302 unminified_dir,
303 import_map: Err(Error::NotFound(None)),
304 }
305 }
306
307 pub(crate) fn text(&self) -> Rc<DOMString> {
308 match &self.code {
309 SourceCode::Text(text) => Rc::clone(text),
310 SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text),
311 }
312 }
313}
314
315fn finish_fetching_a_script(
317 elem: &HTMLScriptElement,
318 script_kind: ExternalScriptKind,
319 cx: &mut JSContext,
320) {
321 let load = elem.result.take().expect("Result must be ready to proceed");
322
323 match script_kind {
325 ExternalScriptKind::Asap => {
326 let document = elem.preparation_time_document.get().unwrap();
327 document.asap_script_loaded(cx, elem, load)
328 },
329 ExternalScriptKind::AsapInOrder => {
330 let document = elem.preparation_time_document.get().unwrap();
331 document.asap_in_order_script_loaded(cx, elem, load)
332 },
333 ExternalScriptKind::Deferred => {
334 let document = elem.parser_document.as_rooted();
335 document.deferred_script_loaded(cx, elem, load);
336 },
337 ExternalScriptKind::ParsingBlocking => {
338 let document = elem.parser_document.as_rooted();
339 document.pending_parsing_blocking_script_loaded(elem, load, cx);
340 },
341 }
342
343 LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
345}
346
347pub(crate) type ScriptResult = Result<Script, ()>;
348
349#[derive(JSTraceable, MallocSizeOf)]
351#[expect(clippy::large_enum_variant)]
352pub(crate) enum Script {
353 Classic(ClassicScript),
354 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
355 ImportMap(ScriptOrigin),
356}
357
358struct ClassicContext {
360 elem: Trusted<HTMLScriptElement>,
362 kind: ExternalScriptKind,
364 character_encoding: &'static Encoding,
367 data: Vec<u8>,
369 metadata: Option<Metadata>,
371 url: ServoUrl,
373 status: Result<(), NetworkError>,
375 fetch_options: ScriptFetchOptions,
377 response_was_cors_cross_origin: bool,
379}
380
381impl FetchResponseListener for ClassicContext {
382 fn process_request_body(&mut self, _: RequestId) {}
384
385 fn process_response(
386 &mut self,
387 _: &mut js::context::JSContext,
388 _: RequestId,
389 metadata: Result<FetchMetadata, NetworkError>,
390 ) {
391 self.metadata = metadata.ok().map(|meta| {
392 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
393 match meta {
394 FetchMetadata::Unfiltered(m) => m,
395 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
396 }
397 });
398
399 let status = self
400 .metadata
401 .as_ref()
402 .map(|m| m.status.clone())
403 .unwrap_or_else(HttpStatus::new_error);
404
405 self.status = {
406 if status.is_error() {
407 Err(NetworkError::ResourceLoadError(
408 "No http status code received".to_owned(),
409 ))
410 } else if status.is_success() {
411 Ok(())
412 } else {
413 Err(NetworkError::ResourceLoadError(format!(
414 "HTTP error code {}",
415 status.code()
416 )))
417 }
418 };
419 }
420
421 fn process_response_chunk(
422 &mut self,
423 _: &mut js::context::JSContext,
424 _: RequestId,
425 mut chunk: Vec<u8>,
426 ) {
427 if self.status.is_ok() {
428 self.data.append(&mut chunk);
429 }
430 }
431
432 fn process_response_eof(
435 mut self,
436 cx: &mut js::context::JSContext,
437 _: RequestId,
438 response: Result<(), NetworkError>,
439 timing: ResourceFetchTiming,
440 ) {
441 network_listener::submit_timing(cx, &self, &response, &timing);
443
444 let elem = self.elem.root();
445
446 match (response.as_ref(), self.status.as_ref()) {
447 (Err(error), _) | (_, Err(error)) => {
448 error!("Fetching classic script failed {:?} ({})", error, self.url);
449 *elem.result.borrow_mut() = Some(Err(()));
451 finish_fetching_a_script(&elem, self.kind, cx);
452 return;
453 },
454 _ => {},
455 };
456
457 let metadata = self.metadata.take().unwrap();
458 let final_url = metadata.final_url;
459
460 let encoding = metadata
463 .charset
464 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
465 .unwrap_or(self.character_encoding);
466
467 let (mut source_text, _, _) = encoding.decode(&self.data);
469
470 let global = elem.global();
471
472 if let Some(window) = global.downcast::<Window>() {
473 substitute_with_local_script(window, &mut source_text, final_url.clone());
474 }
475
476 let muted_errors = self.response_was_cors_cross_origin;
478
479 let script = global.create_a_classic_script(
482 cx,
483 source_text,
484 final_url,
485 self.fetch_options.clone(),
486 ErrorReporting::from(muted_errors),
487 Some(IntroductionType::SRC_SCRIPT),
488 1,
489 true,
490 );
491
492 *elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
523 finish_fetching_a_script(&elem, self.kind, cx);
524 }
526
527 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
528 let global = &self.resource_timing_global();
529 let elem = self.elem.root();
530 global.report_csp_violations(violations, Some(elem.upcast()), None);
531 }
532}
533
534impl ResourceTimingListener for ClassicContext {
535 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
536 let initiator_type = InitiatorType::LocalName(
537 self.elem
538 .root()
539 .upcast::<Element>()
540 .local_name()
541 .to_string(),
542 );
543 (initiator_type, self.url.clone())
544 }
545
546 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
547 self.elem.root().owner_document().global()
548 }
549}
550
551#[allow(clippy::too_many_arguments)]
554pub(crate) fn script_fetch_request(
555 webview_id: WebViewId,
556 url: ServoUrl,
557 cors_setting: Option<CorsSettings>,
558 options: ScriptFetchOptions,
559 referrer: Referrer,
560) -> RequestBuilder {
561 create_a_potential_cors_request(
564 Some(webview_id),
565 url,
566 Destination::Script,
567 cors_setting,
568 None,
569 referrer,
570 )
571 .parser_metadata(options.parser_metadata)
572 .integrity_metadata(options.integrity_metadata.clone())
573 .referrer_policy(options.referrer_policy)
574 .cryptographic_nonce_metadata(options.cryptographic_nonce)
575}
576
577fn fetch_a_classic_script(
579 script: &HTMLScriptElement,
580 kind: ExternalScriptKind,
581 url: ServoUrl,
582 cors_setting: Option<CorsSettings>,
583 options: ScriptFetchOptions,
584 character_encoding: &'static Encoding,
585) {
586 let doc = script.owner_document();
588 let global = script.global();
589 let referrer = global.get_referrer();
590 let request = script_fetch_request(
591 doc.webview_id(),
592 url.clone(),
593 cors_setting,
594 options.clone(),
595 referrer,
596 )
597 .with_global_scope(&global);
598
599 let context = ClassicContext {
602 elem: Trusted::new(script),
603 kind,
604 character_encoding,
605 data: vec![],
606 metadata: None,
607 url,
608 status: Ok(()),
609 fetch_options: options,
610 response_was_cors_cross_origin: false,
611 };
612 doc.fetch_background(request, context);
613}
614
615impl HTMLScriptElement {
616 pub(crate) fn set_initial_script_text(&self) {
618 *self.script_text.borrow_mut() = self.text();
619 }
620
621 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
623 if self.script_text.borrow().clone() != self.text() {
627 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
628 cx,
629 &self.owner_global(),
630 self.Text(),
631 "HTMLScriptElement text",
632 )?;
633 }
634
635 Ok(())
636 }
637
638 fn has_render_blocking_attribute(&self) -> bool {
639 self.blocking
640 .get()
641 .is_some_and(|list| list.Contains("render".into()))
642 }
643
644 fn potentially_render_blocking(&self) -> bool {
646 if self.has_render_blocking_attribute() {
650 return true;
651 }
652 let element = self.upcast::<Element>();
653 self.get_script_type()
657 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
658 self.parser_inserted.get() &&
659 !element.has_attribute(&local_name!("async")) &&
660 !element.has_attribute(&local_name!("defer"))
661 }
662
663 pub(crate) fn prepare(
665 &self,
666 cx: &mut JSContext,
667 introduction_type_override: Option<&'static CStr>,
668 ) {
669 let introduction_type =
670 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
671
672 if self.already_started.get() {
674 return;
675 }
676
677 let was_parser_inserted = self.parser_inserted.get();
682 self.parser_inserted.set(false);
683
684 let element = self.upcast::<Element>();
687 let asynch = element.has_attribute(&local_name!("async"));
688 if was_parser_inserted && !asynch {
690 self.non_blocking.set(true);
691 }
692
693 if self.prepare_the_script_text(cx).is_err() {
696 return;
697 }
698 let text = self.script_text.borrow().clone();
700 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
702 return;
703 }
704
705 if !self.upcast::<Node>().is_connected() {
707 return;
708 }
709
710 let script_type = if let Some(ty) = self.get_script_type() {
711 ty
713 } else {
714 return;
716 };
717
718 if was_parser_inserted {
722 self.parser_inserted.set(true);
723 self.non_blocking.set(false);
724 }
725
726 self.already_started.set(true);
728
729 let doc = self.owner_document();
731 self.preparation_time_document.set(Some(&doc));
732
733 if self.parser_inserted.get() && *self.parser_document != *doc {
737 return;
738 }
739
740 if !doc.scripting_enabled() {
742 return;
743 }
744
745 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
747 return;
748 }
749
750 let global = &doc.global();
751
752 if !element.has_attribute(&local_name!("src")) &&
754 global
755 .get_csp_list()
756 .should_elements_inline_type_behavior_be_blocked(
757 global,
758 element,
759 InlineCheckType::Script,
760 &text.str(),
761 self.line_number as u32,
762 )
763 {
764 warn!("Blocking inline script due to CSP");
765 return;
766 }
767
768 if script_type == ScriptType::Classic {
770 let for_attribute = element.get_attribute(&local_name!("for"));
771 let event_attribute = element.get_attribute(&local_name!("event"));
772 if let (Some(ref for_attribute), Some(ref event_attribute)) =
773 (for_attribute, event_attribute)
774 {
775 let for_value = for_attribute.value().to_ascii_lowercase();
776 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
777 if for_value != "window" {
778 return;
779 }
780
781 let event_value = event_attribute.value().to_ascii_lowercase();
782 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
783 if event_value != "onload" && event_value != "onload()" {
784 return;
785 }
786 }
787 }
788
789 let encoding = element
794 .get_attribute(&local_name!("charset"))
795 .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
796 .unwrap_or_else(|| doc.encoding());
797
798 let cors_setting = cors_setting_for_element(element);
800
801 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
803
804 let cryptographic_nonce =
809 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
810 element.nonce_value().trim().to_owned()
811 } else {
812 String::new()
813 };
814
815 let im_attribute = element.get_attribute(&local_name!("integrity"));
818 let integrity_val = im_attribute.as_ref().map(|a| a.value());
819 let integrity_metadata = match integrity_val {
820 Some(ref value) => &***value,
821 None => "",
822 };
823
824 let referrer_policy = referrer_policy_for_element(element);
826
827 let parser_metadata = if self.parser_inserted.get() {
832 ParserMetadata::ParserInserted
833 } else {
834 ParserMetadata::NotParserInserted
835 };
836
837 let mut options = ScriptFetchOptions {
839 cryptographic_nonce,
840 integrity_metadata: integrity_metadata.to_owned(),
841 parser_metadata,
842 referrer_policy,
843 credentials_mode: module_credentials_mode,
844 render_blocking: false,
845 };
846
847 let base_url = doc.base_url();
850
851 let kind = self.get_script_kind(script_type);
852 let delayed_document = self.get_script_active_document(kind);
853
854 if let Some(src) = element.get_attribute(&local_name!("src")) {
855 if script_type == ScriptType::ImportMap {
859 self.queue_error_event();
862 return;
863 }
864
865 let src = src.value();
867
868 if src.is_empty() {
870 self.queue_error_event();
871 return;
872 }
873
874 self.from_an_external_file.set(true);
876
877 let url = match base_url.join(&src) {
879 Ok(url) => url,
880 Err(_) => {
881 warn!("error parsing URL for script {}", &**src);
882 self.queue_error_event();
883 return;
884 },
885 };
886
887 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
889 self.marked_as_render_blocking.set(true);
890 doc.increment_render_blocking_element_count();
891 }
892
893 self.delay_load_event(&delayed_document, url.clone());
895
896 if self.marked_as_render_blocking.get() {
898 options.render_blocking = true;
899 }
900
901 match script_type {
903 ScriptType::Classic => {
904 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
906 },
907 ScriptType::Module => {
908 if integrity_val.is_none() {
911 options.integrity_metadata = global
912 .import_map()
913 .resolve_a_module_integrity_metadata(&url);
914 }
915
916 let script = DomRoot::from_ref(self);
917
918 fetch_an_external_module_script(
920 cx,
921 url,
922 global,
923 options,
924 move |cx, module_tree| {
925 let load = module_tree.map(Script::Module).ok_or(());
926 *script.result.borrow_mut() = Some(load);
927
928 finish_fetching_a_script(&script, kind, cx);
929 },
930 );
931 },
932 ScriptType::ImportMap => (),
933 }
934 } else {
935 assert!(!text.is_empty());
938
939 let text_rc = Rc::new(text.clone());
940
941 match script_type {
943 ScriptType::Classic => {
944 let script = self.global().create_a_classic_script(
947 cx,
948 std::borrow::Cow::Borrowed(&text.str()),
949 base_url,
950 options,
951 ErrorReporting::Unmuted,
952 introduction_type,
953 self.line_number as u32,
954 false,
955 );
956 let result = Ok(Script::Classic(script));
957
958 if was_parser_inserted &&
959 doc.get_current_parser()
960 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
961 doc.get_script_blocking_stylesheets_count() > 0
962 {
963 doc.set_pending_parsing_blocking_script(self, Some(result));
965 } else {
966 self.execute(cx, result);
968 }
969 return;
970 },
971 ScriptType::Module => {
972 self.delay_load_event(&delayed_document, base_url.clone());
974
975 if self.potentially_render_blocking() &&
977 doc.allows_adding_render_blocking_elements()
978 {
979 self.marked_as_render_blocking.set(true);
981 doc.increment_render_blocking_element_count();
982
983 options.render_blocking = true;
985 }
986
987 let script = DomRoot::from_ref(self);
988 fetch_inline_module_script(
991 cx,
992 global,
993 text_rc,
994 base_url,
995 options,
996 self.line_number as u32,
997 introduction_type,
998 move |_, module_tree| {
999 let load = module_tree.map(Script::Module).ok_or(());
1000 *script.result.borrow_mut() = Some(load);
1001
1002 let trusted = Trusted::new(&*script);
1003
1004 script
1006 .owner_global()
1007 .task_manager()
1008 .networking_task_source()
1009 .queue(task!(terminate_module_fetch: move |cx| {
1010 finish_fetching_a_script(&trusted.root(), kind, cx);
1012 }));
1013 },
1014 );
1015 },
1016 ScriptType::ImportMap => {
1017 let import_map_result =
1020 parse_an_import_map_string(global, Rc::clone(&text_rc), base_url.clone());
1021 let script = Script::ImportMap(ScriptOrigin::internal(
1022 text_rc,
1023 base_url,
1024 options,
1025 script_type,
1026 self.global().unminified_js_dir(),
1027 import_map_result,
1028 ));
1029
1030 self.execute(cx, Ok(script));
1032 return;
1033 },
1034 }
1035 }
1036
1037 match kind {
1039 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
1040 ExternalScriptKind::ParsingBlocking => {
1041 delayed_document.set_pending_parsing_blocking_script(self, None);
1042 },
1043 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
1044 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
1045 }
1046 }
1047
1048 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
1050 let doc = self.owner_document();
1052
1053 if *doc != *self.preparation_time_document.get().unwrap() {
1055 return;
1056 }
1057
1058 if self.marked_as_render_blocking.replace(false) {
1060 self.marked_as_render_blocking.set(false);
1061 doc.decrement_render_blocking_element_count();
1062 }
1063
1064 let script = match result {
1065 Err(_) => {
1067 self.dispatch_event(cx, atom!("error"));
1068 return;
1069 },
1070
1071 Ok(script) => script,
1072 };
1073
1074 let neutralized_doc =
1078 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
1079 let doc = self.owner_document();
1080 doc.incr_ignore_destructive_writes_counter();
1081 Some(doc)
1082 } else {
1083 None
1084 };
1085
1086 let document = self.owner_document();
1087
1088 match script {
1089 Script::Classic(script) => {
1090 let old_script = document.GetCurrentScript();
1092
1093 if self.upcast::<Node>().is_in_a_shadow_tree() {
1096 document.set_current_script(None)
1097 } else {
1098 document.set_current_script(Some(self))
1099 }
1100
1101 _ = self
1103 .owner_global()
1104 .run_a_classic_script(cx, script, RethrowErrors::No);
1105
1106 document.set_current_script(old_script.as_deref());
1108 },
1109 Script::Module(module_tree) => {
1110 document.set_current_script(None);
1112
1113 self.owner_global()
1115 .run_a_module_script(cx, module_tree, false);
1116 },
1117 Script::ImportMap(script) => {
1118 register_import_map(cx, &self.owner_global(), script.import_map);
1120 },
1121 }
1122
1123 if let Some(doc) = neutralized_doc {
1126 doc.decr_ignore_destructive_writes_counter();
1127 }
1128
1129 if self.from_an_external_file.get() {
1131 self.dispatch_event(cx, atom!("load"));
1132 }
1133 }
1134
1135 pub(crate) fn queue_error_event(&self) {
1136 self.owner_global()
1137 .task_manager()
1138 .dom_manipulation_task_source()
1139 .queue_simple_event(self.upcast(), atom!("error"));
1140 }
1141
1142 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1144 let element = self.upcast::<Element>();
1145
1146 let type_attr = element.get_attribute(&local_name!("type"));
1147 let language_attr = element.get_attribute(&local_name!("language"));
1148
1149 match (
1150 type_attr.as_ref().map(|t| t.value()),
1151 language_attr.as_ref().map(|l| l.value()),
1152 ) {
1153 (Some(ref ty), _) if ty.is_empty() => {
1154 debug!("script type empty, inferring js");
1155 Some(ScriptType::Classic)
1156 },
1157 (None, Some(ref lang)) if lang.is_empty() => {
1158 debug!("script type empty, inferring js");
1159 Some(ScriptType::Classic)
1160 },
1161 (None, None) => {
1162 debug!("script type empty, inferring js");
1163 Some(ScriptType::Classic)
1164 },
1165 (None, Some(ref lang)) => {
1166 debug!("script language={}", &***lang);
1167 let language = format!("text/{}", &***lang);
1168
1169 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1170 Some(ScriptType::Classic)
1171 } else {
1172 None
1173 }
1174 },
1175 (Some(ref ty), _) => {
1176 debug!("script type={}", &***ty);
1177
1178 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1179 return Some(ScriptType::Module);
1180 }
1181
1182 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1183 return Some(ScriptType::ImportMap);
1184 }
1185
1186 if SCRIPT_JS_MIMES
1187 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1188 {
1189 Some(ScriptType::Classic)
1190 } else {
1191 None
1192 }
1193 },
1194 }
1195 }
1196
1197 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1198 self.parser_inserted.set(parser_inserted);
1199 }
1200
1201 pub(crate) fn get_parser_inserted(&self) -> bool {
1202 self.parser_inserted.get()
1203 }
1204
1205 pub(crate) fn set_already_started(&self, already_started: bool) {
1206 self.already_started.set(already_started);
1207 }
1208
1209 pub(crate) fn get_non_blocking(&self) -> bool {
1210 self.non_blocking.get()
1211 }
1212
1213 fn dispatch_event(&self, cx: &mut JSContext, type_: Atom) -> bool {
1214 let window = self.owner_window();
1215 let event = Event::new(
1216 window.upcast(),
1217 type_,
1218 EventBubbles::DoesNotBubble,
1219 EventCancelable::NotCancelable,
1220 CanGc::from_cx(cx),
1221 );
1222 event.fire_with_cx(cx, self.upcast())
1223 }
1224
1225 fn text(&self) -> DOMString {
1226 match self.Text() {
1227 TrustedScriptOrString::String(value) => value,
1228 TrustedScriptOrString::TrustedScript(trusted_script) => {
1229 DOMString::from(trusted_script.to_string())
1230 },
1231 }
1232 }
1233}
1234
1235impl VirtualMethods for HTMLScriptElement {
1236 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1237 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1238 }
1239
1240 fn attribute_mutated(
1241 &self,
1242 cx: &mut js::context::JSContext,
1243 attr: AttrRef<'_>,
1244 mutation: AttributeMutation,
1245 ) {
1246 self.super_type()
1247 .unwrap()
1248 .attribute_mutated(cx, attr, mutation);
1249 if *attr.local_name() == local_name!("src") {
1250 if let AttributeMutation::Set(..) = mutation &&
1251 !self.parser_inserted.get() &&
1252 self.upcast::<Node>().is_connected()
1253 {
1254 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1255 }
1256 } else if *attr.local_name() == local_name!("blocking") &&
1257 !self.has_render_blocking_attribute() &&
1258 self.marked_as_render_blocking.replace(false)
1259 {
1260 let document = self.owner_document();
1261 document.decrement_render_blocking_element_count();
1262 }
1263 }
1264
1265 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1267 if let Some(s) = self.super_type() {
1268 s.children_changed(cx, mutation);
1269 }
1270
1271 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1272 let script = DomRoot::from_ref(self);
1273 self.owner_document().add_delayed_task(
1277 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1278 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1279 }),
1280 );
1281 }
1282 }
1283
1284 fn post_connection_steps(&self, cx: &mut JSContext) {
1286 if let Some(s) = self.super_type() {
1287 s.post_connection_steps(cx);
1288 }
1289
1290 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1291 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1292 }
1293 }
1294
1295 fn cloning_steps(
1296 &self,
1297 cx: &mut JSContext,
1298 copy: &Node,
1299 maybe_doc: Option<&Document>,
1300 clone_children: CloneChildrenFlag,
1301 ) {
1302 if let Some(s) = self.super_type() {
1303 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1304 }
1305
1306 if self.already_started.get() {
1308 copy.downcast::<HTMLScriptElement>()
1309 .unwrap()
1310 .set_already_started(true);
1311 }
1312 }
1313
1314 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1315 self.super_type().unwrap().unbind_from_tree(cx, context);
1316
1317 if self.marked_as_render_blocking.replace(false) {
1318 let document = self.owner_document();
1319 document.decrement_render_blocking_element_count();
1320 }
1321 }
1322}
1323
1324impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1325 fn Src(&self) -> TrustedScriptURLOrUSVString {
1327 let element = self.upcast::<Element>();
1328 element.get_trusted_type_url_attribute(&local_name!("src"))
1329 }
1330
1331 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1333 let element = self.upcast::<Element>();
1334 let local_name = &local_name!("src");
1335 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1336 cx,
1337 &element.owner_global(),
1338 value,
1339 &format!("HTMLScriptElement {}", local_name),
1340 )?;
1341 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1342 Ok(())
1343 }
1344
1345 make_getter!(Type, "type");
1347 make_setter!(SetType, "type");
1349
1350 make_getter!(Charset, "charset");
1352 make_setter!(SetCharset, "charset");
1354
1355 fn Async(&self) -> bool {
1357 self.non_blocking.get() ||
1358 self.upcast::<Element>()
1359 .has_attribute(&local_name!("async"))
1360 }
1361
1362 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1364 self.non_blocking.set(false);
1365 self.upcast::<Element>()
1366 .set_bool_attribute(cx, &local_name!("async"), value);
1367 }
1368
1369 make_bool_getter!(Defer, "defer");
1371 make_bool_setter!(SetDefer, "defer");
1373
1374 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1376 self.blocking.or_init(|| {
1377 DOMTokenList::new(
1378 cx,
1379 self.upcast(),
1380 &local_name!("blocking"),
1381 Some(vec![Atom::from("render")]),
1382 )
1383 })
1384 }
1385
1386 make_bool_getter!(NoModule, "nomodule");
1388 make_bool_setter!(SetNoModule, "nomodule");
1390
1391 make_getter!(Integrity, "integrity");
1393 make_setter!(SetIntegrity, "integrity");
1395
1396 make_getter!(Event, "event");
1398 make_setter!(SetEvent, "event");
1400
1401 make_getter!(HtmlFor, "for");
1403 make_setter!(SetHtmlFor, "for");
1405
1406 fn GetCrossOrigin(&self) -> Option<DOMString> {
1408 reflect_cross_origin_attribute(self.upcast::<Element>())
1409 }
1410
1411 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1413 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1414 }
1415
1416 fn ReferrerPolicy(&self) -> DOMString {
1418 reflect_referrer_policy_attribute(self.upcast::<Element>())
1419 }
1420
1421 make_setter!(SetReferrerPolicy, "referrerpolicy");
1423
1424 fn InnerText(&self) -> TrustedScriptOrString {
1426 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1428 }
1429
1430 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1432 let value = TrustedScript::get_trusted_type_compliant_string(
1435 cx,
1436 &self.owner_global(),
1437 input,
1438 "HTMLScriptElement innerText",
1439 )?;
1440 *self.script_text.borrow_mut() = value.clone();
1441 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1443 Ok(())
1444 }
1445
1446 fn Text(&self) -> TrustedScriptOrString {
1448 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1449 }
1450
1451 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1453 let value = TrustedScript::get_trusted_type_compliant_string(
1456 cx,
1457 &self.owner_global(),
1458 value,
1459 "HTMLScriptElement text",
1460 )?;
1461 *self.script_text.borrow_mut() = value.clone();
1463 Node::string_replace_all(cx, value, self.upcast::<Node>());
1465 Ok(())
1466 }
1467
1468 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1470 Some(TrustedScriptOrString::String(
1472 self.upcast::<Node>().GetTextContent()?,
1473 ))
1474 }
1475
1476 fn SetTextContent(
1478 &self,
1479 cx: &mut JSContext,
1480 value: Option<TrustedScriptOrString>,
1481 ) -> Fallible<()> {
1482 let value = TrustedScript::get_trusted_type_compliant_string(
1485 cx,
1486 &self.owner_global(),
1487 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1488 "HTMLScriptElement textContent",
1489 )?;
1490 *self.script_text.borrow_mut() = value.clone();
1492 self.upcast::<Node>()
1494 .set_text_content_for_element(cx, Some(value));
1495 Ok(())
1496 }
1497
1498 fn Supports(_window: &Window, type_: DOMString) -> bool {
1500 matches!(&*type_.str(), "classic" | "module" | "importmap")
1503 }
1504}
1505
1506pub(crate) fn substitute_with_local_script(
1507 window: &Window,
1508 script: &mut Cow<'_, str>,
1509 url: ServoUrl,
1510) {
1511 if window.local_script_source().is_none() {
1512 return;
1513 }
1514 let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1515 path = path.join(&url[url::Position::BeforeHost..]);
1516 debug!("Attempting to read script stored at: {:?}", path);
1517 match read_to_string(path.clone()) {
1518 Ok(local_script) => {
1519 debug!("Found script stored at: {:?}", path);
1520 *script = Cow::Owned(local_script);
1521 },
1522 Err(why) => warn!("Could not restore script from file {:?}", why),
1523 }
1524}
1525
1526#[derive(Clone, Copy)]
1527enum ExternalScriptKind {
1528 Deferred,
1529 ParsingBlocking,
1530 AsapInOrder,
1531 Asap,
1532}