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 servo_base::id::WebViewId;
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::{LoadBlocker, LoadType};
30use crate::dom::attr::Attr;
31use crate::dom::bindings::cell::DomRefCell;
32use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
33use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
34use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
35use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
36use crate::dom::bindings::codegen::UnionTypes::{
37 TrustedScriptOrString, TrustedScriptURLOrUSVString,
38};
39use crate::dom::bindings::error::{Error, Fallible};
40use crate::dom::bindings::inheritance::Castable;
41use crate::dom::bindings::refcounted::Trusted;
42use crate::dom::bindings::reflector::DomGlobal;
43use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
44use crate::dom::bindings::str::DOMString;
45use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
46use crate::dom::document::Document;
47use crate::dom::domtokenlist::DOMTokenList;
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(self.upcast(), CanGc::from_cx(cx))
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: &Attr,
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 if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1252 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1253 }
1254 }
1255 } else if *attr.local_name() == local_name!("blocking") &&
1256 !self.has_render_blocking_attribute() &&
1257 self.marked_as_render_blocking.replace(false)
1258 {
1259 let document = self.owner_document();
1260 document.decrement_render_blocking_element_count();
1261 }
1262 }
1263
1264 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1266 if let Some(s) = self.super_type() {
1267 s.children_changed(cx, mutation);
1268 }
1269
1270 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1271 let script = DomRoot::from_ref(self);
1272 self.owner_document().add_delayed_task(
1276 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1277 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1278 }),
1279 );
1280 }
1281 }
1282
1283 fn post_connection_steps(&self, cx: &mut JSContext) {
1285 if let Some(s) = self.super_type() {
1286 s.post_connection_steps(cx);
1287 }
1288
1289 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1290 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1291 }
1292 }
1293
1294 fn cloning_steps(
1295 &self,
1296 cx: &mut JSContext,
1297 copy: &Node,
1298 maybe_doc: Option<&Document>,
1299 clone_children: CloneChildrenFlag,
1300 ) {
1301 if let Some(s) = self.super_type() {
1302 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1303 }
1304
1305 if self.already_started.get() {
1307 copy.downcast::<HTMLScriptElement>()
1308 .unwrap()
1309 .set_already_started(true);
1310 }
1311 }
1312
1313 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1314 self.super_type().unwrap().unbind_from_tree(cx, context);
1315
1316 if self.marked_as_render_blocking.replace(false) {
1317 let document = self.owner_document();
1318 document.decrement_render_blocking_element_count();
1319 }
1320 }
1321}
1322
1323impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1324 fn Src(&self) -> TrustedScriptURLOrUSVString {
1326 let element = self.upcast::<Element>();
1327 element.get_trusted_type_url_attribute(&local_name!("src"))
1328 }
1329
1330 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1332 let element = self.upcast::<Element>();
1333 let local_name = &local_name!("src");
1334 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1335 cx,
1336 &element.owner_global(),
1337 value,
1338 &format!("HTMLScriptElement {}", local_name),
1339 )?;
1340 element.set_attribute(
1341 local_name,
1342 AttrValue::String(value.str().to_owned()),
1343 CanGc::from_cx(cx),
1344 );
1345 Ok(())
1346 }
1347
1348 make_getter!(Type, "type");
1350 make_setter!(SetType, "type");
1352
1353 make_getter!(Charset, "charset");
1355 make_setter!(SetCharset, "charset");
1357
1358 fn Async(&self) -> bool {
1360 self.non_blocking.get() ||
1361 self.upcast::<Element>()
1362 .has_attribute(&local_name!("async"))
1363 }
1364
1365 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1367 self.non_blocking.set(false);
1368 self.upcast::<Element>().set_bool_attribute(
1369 &local_name!("async"),
1370 value,
1371 CanGc::from_cx(cx),
1372 );
1373 }
1374
1375 make_bool_getter!(Defer, "defer");
1377 make_bool_setter!(SetDefer, "defer");
1379
1380 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1382 self.blocking.or_init(|| {
1383 DOMTokenList::new(
1384 self.upcast(),
1385 &local_name!("blocking"),
1386 Some(vec![Atom::from("render")]),
1387 CanGc::from_cx(cx),
1388 )
1389 })
1390 }
1391
1392 make_bool_getter!(NoModule, "nomodule");
1394 make_bool_setter!(SetNoModule, "nomodule");
1396
1397 make_getter!(Integrity, "integrity");
1399 make_setter!(SetIntegrity, "integrity");
1401
1402 make_getter!(Event, "event");
1404 make_setter!(SetEvent, "event");
1406
1407 make_getter!(HtmlFor, "for");
1409 make_setter!(SetHtmlFor, "for");
1411
1412 fn GetCrossOrigin(&self) -> Option<DOMString> {
1414 reflect_cross_origin_attribute(self.upcast::<Element>())
1415 }
1416
1417 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1419 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1420 }
1421
1422 fn ReferrerPolicy(&self) -> DOMString {
1424 reflect_referrer_policy_attribute(self.upcast::<Element>())
1425 }
1426
1427 make_setter!(SetReferrerPolicy, "referrerpolicy");
1429
1430 fn InnerText(&self) -> TrustedScriptOrString {
1432 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1434 }
1435
1436 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1438 let value = TrustedScript::get_trusted_type_compliant_string(
1441 cx,
1442 &self.owner_global(),
1443 input,
1444 "HTMLScriptElement innerText",
1445 )?;
1446 *self.script_text.borrow_mut() = value.clone();
1447 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1449 Ok(())
1450 }
1451
1452 fn Text(&self) -> TrustedScriptOrString {
1454 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1455 }
1456
1457 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1459 let value = TrustedScript::get_trusted_type_compliant_string(
1462 cx,
1463 &self.owner_global(),
1464 value,
1465 "HTMLScriptElement text",
1466 )?;
1467 *self.script_text.borrow_mut() = value.clone();
1469 Node::string_replace_all(cx, value, self.upcast::<Node>());
1471 Ok(())
1472 }
1473
1474 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1476 Some(TrustedScriptOrString::String(
1478 self.upcast::<Node>().GetTextContent()?,
1479 ))
1480 }
1481
1482 fn SetTextContent(
1484 &self,
1485 cx: &mut JSContext,
1486 value: Option<TrustedScriptOrString>,
1487 ) -> Fallible<()> {
1488 let value = TrustedScript::get_trusted_type_compliant_string(
1491 cx,
1492 &self.owner_global(),
1493 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1494 "HTMLScriptElement textContent",
1495 )?;
1496 *self.script_text.borrow_mut() = value.clone();
1498 self.upcast::<Node>()
1500 .set_text_content_for_element(cx, Some(value));
1501 Ok(())
1502 }
1503
1504 fn Supports(_window: &Window, type_: DOMString) -> bool {
1506 matches!(&*type_.str(), "classic" | "module" | "importmap")
1509 }
1510}
1511
1512pub(crate) fn substitute_with_local_script(
1513 window: &Window,
1514 script: &mut Cow<'_, str>,
1515 url: ServoUrl,
1516) {
1517 if window.local_script_source().is_none() {
1518 return;
1519 }
1520 let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1521 path = path.join(&url[url::Position::BeforeHost..]);
1522 debug!("Attempting to read script stored at: {:?}", path);
1523 match read_to_string(path.clone()) {
1524 Ok(local_script) => {
1525 debug!("Found script stored at: {:?}", path);
1526 *script = Cow::Owned(local_script);
1527 },
1528 Err(why) => warn!("Could not restore script from file {:?}", why),
1529 }
1530}
1531
1532#[derive(Clone, Copy)]
1533enum ExternalScriptKind {
1534 Deferred,
1535 ParsingBlocking,
1536 AsapInOrder,
1537 Asap,
1538}