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::eventtarget::EventTarget;
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::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)]
351pub(crate) enum Script {
352 Classic(ClassicScript),
353 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
354 ImportMap(Fallible<ImportMap>),
355}
356
357struct ClassicContext {
359 elem: Trusted<HTMLScriptElement>,
361 kind: ExternalScriptKind,
363 character_encoding: &'static Encoding,
366 data: Vec<u8>,
368 metadata: Option<Metadata>,
370 url: ServoUrl,
372 status: Result<(), NetworkError>,
374 fetch_options: ScriptFetchOptions,
376 response_was_cors_cross_origin: bool,
378}
379
380impl FetchResponseListener for ClassicContext {
381 fn process_request_body(&mut self, _: RequestId) {}
383
384 fn process_response(
385 &mut self,
386 _: &mut js::context::JSContext,
387 _: RequestId,
388 metadata: Result<FetchMetadata, NetworkError>,
389 ) {
390 self.metadata = metadata.ok().map(|meta| {
391 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
392 match meta {
393 FetchMetadata::Unfiltered(m) => m,
394 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
395 }
396 });
397
398 let status = self
399 .metadata
400 .as_ref()
401 .map(|m| m.status.clone())
402 .unwrap_or_else(HttpStatus::new_error);
403
404 self.status = {
405 if status.is_error() {
406 Err(NetworkError::ResourceLoadError(
407 "No http status code received".to_owned(),
408 ))
409 } else if status.is_success() {
410 Ok(())
411 } else {
412 Err(NetworkError::ResourceLoadError(format!(
413 "HTTP error code {}",
414 status.code()
415 )))
416 }
417 };
418 }
419
420 fn process_response_chunk(
421 &mut self,
422 _: &mut js::context::JSContext,
423 _: RequestId,
424 mut chunk: Vec<u8>,
425 ) {
426 if self.status.is_ok() {
427 self.data.append(&mut chunk);
428 }
429 }
430
431 fn process_response_eof(
434 mut self,
435 cx: &mut js::context::JSContext,
436 _: RequestId,
437 response: Result<(), NetworkError>,
438 timing: ResourceFetchTiming,
439 ) {
440 network_listener::submit_timing(cx, &self, &response, &timing);
442
443 let elem = self.elem.root();
444
445 match (response.as_ref(), self.status.as_ref()) {
446 (Err(error), _) | (_, Err(error)) => {
447 error!("Fetching classic script failed {:?} ({})", error, self.url);
448 *elem.result.borrow_mut() = Some(Err(()));
450 finish_fetching_a_script(&elem, self.kind, cx);
451 return;
452 },
453 _ => {},
454 };
455
456 let metadata = self.metadata.take().unwrap();
457 let final_url = metadata.final_url;
458
459 let encoding = metadata
462 .charset
463 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
464 .unwrap_or(self.character_encoding);
465
466 let (mut source_text, _, _) = encoding.decode(&self.data);
468
469 let global = elem.global();
470
471 if let Some(window) = global.downcast::<Window>() {
472 substitute_with_local_script(window, &mut source_text, final_url.clone());
473 }
474
475 let muted_errors = self.response_was_cors_cross_origin;
477
478 let script = global.create_a_classic_script(
481 cx,
482 source_text,
483 final_url,
484 self.fetch_options.clone(),
485 ErrorReporting::from(muted_errors),
486 Some(IntroductionType::SRC_SCRIPT),
487 1,
488 true,
489 );
490
491 *elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
522 finish_fetching_a_script(&elem, self.kind, cx);
523 }
525
526 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
527 let global = &self.resource_timing_global();
528 let elem = self.elem.root();
529 global.report_csp_violations(violations, Some(elem.upcast()), None);
530 }
531}
532
533impl ResourceTimingListener for ClassicContext {
534 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
535 let initiator_type = InitiatorType::LocalName(
536 self.elem
537 .root()
538 .upcast::<Element>()
539 .local_name()
540 .to_string(),
541 );
542 (initiator_type, self.url.clone())
543 }
544
545 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
546 self.elem.root().owner_document().global()
547 }
548}
549
550#[allow(clippy::too_many_arguments)]
553pub(crate) fn script_fetch_request(
554 webview_id: WebViewId,
555 url: ServoUrl,
556 cors_setting: Option<CorsSettings>,
557 options: ScriptFetchOptions,
558 referrer: Referrer,
559) -> RequestBuilder {
560 create_a_potential_cors_request(
563 Some(webview_id),
564 url,
565 Destination::Script,
566 cors_setting,
567 None,
568 referrer,
569 )
570 .parser_metadata(options.parser_metadata)
571 .integrity_metadata(options.integrity_metadata.clone())
572 .referrer_policy(options.referrer_policy)
573 .cryptographic_nonce_metadata(options.cryptographic_nonce)
574}
575
576fn fetch_a_classic_script(
578 script: &HTMLScriptElement,
579 kind: ExternalScriptKind,
580 url: ServoUrl,
581 cors_setting: Option<CorsSettings>,
582 options: ScriptFetchOptions,
583 character_encoding: &'static Encoding,
584) {
585 let doc = script.owner_document();
587 let global = script.global();
588 let referrer = global.get_referrer();
589 let request = script_fetch_request(
590 doc.webview_id(),
591 url.clone(),
592 cors_setting,
593 options.clone(),
594 referrer,
595 )
596 .with_global_scope(&global);
597
598 let context = ClassicContext {
601 elem: Trusted::new(script),
602 kind,
603 character_encoding,
604 data: vec![],
605 metadata: None,
606 url,
607 status: Ok(()),
608 fetch_options: options,
609 response_was_cors_cross_origin: false,
610 };
611 doc.fetch_background(request, context);
612}
613
614impl HTMLScriptElement {
615 pub(crate) fn set_initial_script_text(&self) {
617 *self.script_text.borrow_mut() = self.text();
618 }
619
620 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
622 if self.script_text.borrow().clone() != self.text() {
626 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
627 cx,
628 &self.owner_global(),
629 self.Text(),
630 "HTMLScriptElement text",
631 )?;
632 }
633
634 Ok(())
635 }
636
637 fn has_render_blocking_attribute(&self) -> bool {
638 self.blocking
639 .get()
640 .is_some_and(|list| list.Contains("render".into()))
641 }
642
643 fn potentially_render_blocking(&self) -> bool {
645 if self.has_render_blocking_attribute() {
649 return true;
650 }
651 let element = self.upcast::<Element>();
652 self.get_script_type()
656 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
657 self.parser_inserted.get() &&
658 !element.has_attribute(&local_name!("async")) &&
659 !element.has_attribute(&local_name!("defer"))
660 }
661
662 pub(crate) fn prepare(
664 &self,
665 cx: &mut JSContext,
666 introduction_type_override: Option<&'static CStr>,
667 ) {
668 let introduction_type =
669 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
670
671 if self.already_started.get() {
673 return;
674 }
675
676 let was_parser_inserted = self.parser_inserted.get();
681 self.parser_inserted.set(false);
682
683 let element = self.upcast::<Element>();
686 let asynch = element.has_attribute(&local_name!("async"));
687 if was_parser_inserted && !asynch {
689 self.non_blocking.set(true);
690 }
691
692 if self.prepare_the_script_text(cx).is_err() {
695 return;
696 }
697 let text = self.script_text.borrow().clone();
699 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
701 return;
702 }
703
704 if !self.upcast::<Node>().is_connected() {
706 return;
707 }
708
709 let script_type = if let Some(ty) = self.get_script_type() {
710 ty
712 } else {
713 return;
715 };
716
717 if was_parser_inserted {
721 self.parser_inserted.set(true);
722 self.non_blocking.set(false);
723 }
724
725 self.already_started.set(true);
727
728 let doc = self.owner_document();
730 self.preparation_time_document.set(Some(&doc));
731
732 if self.parser_inserted.get() && *self.parser_document != *doc {
736 return;
737 }
738
739 if !doc.scripting_enabled() {
741 return;
742 }
743
744 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
746 return;
747 }
748
749 let global = &doc.global();
750
751 if !element.has_attribute(&local_name!("src")) &&
753 global
754 .get_csp_list()
755 .should_elements_inline_type_behavior_be_blocked(
756 global,
757 element,
758 InlineCheckType::Script,
759 &text.str(),
760 self.line_number as u32,
761 )
762 {
763 warn!("Blocking inline script due to CSP");
764 return;
765 }
766
767 if script_type == ScriptType::Classic {
769 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
770 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
771 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
772 let for_value = for_attribute.to_ascii_lowercase();
773 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
774 if for_value != "window" {
775 return;
776 }
777
778 let event_value = event_attribute.to_ascii_lowercase();
779 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
780 if event_value != "onload" && event_value != "onload()" {
781 return;
782 }
783 }
784 }
785
786 let encoding = element
791 .get_attribute_string_value(&local_name!("charset"))
792 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
793 .unwrap_or_else(|| doc.encoding());
794
795 let cors_setting = cors_setting_for_element(element);
797
798 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
800
801 let cryptographic_nonce =
806 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
807 element.nonce_value().trim().to_owned()
808 } else {
809 String::new()
810 };
811
812 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
815 let integrity_val_is_none = integrity_val.is_none();
816 let integrity_metadata = integrity_val.unwrap_or_default();
817
818 let referrer_policy = referrer_policy_for_element(element);
820
821 let parser_metadata = if self.parser_inserted.get() {
826 ParserMetadata::ParserInserted
827 } else {
828 ParserMetadata::NotParserInserted
829 };
830
831 let mut options = ScriptFetchOptions {
833 cryptographic_nonce,
834 integrity_metadata,
835 parser_metadata,
836 referrer_policy,
837 credentials_mode: module_credentials_mode,
838 render_blocking: false,
839 };
840
841 let base_url = doc.base_url();
844
845 let kind = self.get_script_kind(script_type);
846 let delayed_document = self.get_script_active_document(kind);
847
848 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
851 if script_type == ScriptType::ImportMap {
853 self.queue_error_event();
856 return;
857 }
858
859 if src.is_empty() {
861 self.queue_error_event();
862 return;
863 }
864
865 self.from_an_external_file.set(true);
867
868 let url = match base_url.join(&src) {
870 Ok(url) => url,
871 Err(_) => {
872 warn!("error parsing URL for script {}", src);
873 self.queue_error_event();
874 return;
875 },
876 };
877
878 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
880 self.marked_as_render_blocking.set(true);
881 doc.increment_render_blocking_element_count();
882 }
883
884 self.delay_load_event(&delayed_document, url.clone());
886
887 if self.marked_as_render_blocking.get() {
889 options.render_blocking = true;
890 }
891
892 match script_type {
894 ScriptType::Classic => {
895 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
897 },
898 ScriptType::Module => {
899 if integrity_val_is_none {
902 options.integrity_metadata = global
903 .import_map()
904 .resolve_a_module_integrity_metadata(&url);
905 }
906
907 let script = DomRoot::from_ref(self);
908
909 fetch_an_external_module_script(
911 cx,
912 url,
913 global,
914 options,
915 move |cx, module_tree| {
916 let load = module_tree.map(Script::Module).ok_or(());
917 *script.result.borrow_mut() = Some(load);
918
919 finish_fetching_a_script(&script, kind, cx);
920 },
921 );
922 },
923 ScriptType::ImportMap => (),
924 }
925 } else {
926 assert!(!text.is_empty());
929
930 let text_rc = Rc::new(text.clone());
931
932 match script_type {
934 ScriptType::Classic => {
935 let script = self.global().create_a_classic_script(
938 cx,
939 std::borrow::Cow::Borrowed(&text.str()),
940 base_url,
941 options,
942 ErrorReporting::Unmuted,
943 introduction_type,
944 self.line_number as u32,
945 false,
946 );
947 let result = Ok(Script::Classic(script));
948
949 if was_parser_inserted &&
950 doc.get_current_parser()
951 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
952 doc.get_script_blocking_stylesheets_count() > 0
953 {
954 doc.set_pending_parsing_blocking_script(self, Some(result));
956 } else {
957 self.execute(cx, result);
959 }
960 return;
961 },
962 ScriptType::Module => {
963 self.delay_load_event(&delayed_document, base_url.clone());
965
966 if self.potentially_render_blocking() &&
968 doc.allows_adding_render_blocking_elements()
969 {
970 self.marked_as_render_blocking.set(true);
972 doc.increment_render_blocking_element_count();
973
974 options.render_blocking = true;
976 }
977
978 let script = DomRoot::from_ref(self);
979 fetch_inline_module_script(
982 cx,
983 global,
984 text_rc,
985 base_url,
986 options,
987 self.line_number as u32,
988 introduction_type,
989 move |_, module_tree| {
990 let load = module_tree.map(Script::Module).ok_or(());
991 *script.result.borrow_mut() = Some(load);
992
993 let trusted = Trusted::new(&*script);
994
995 script
997 .owner_global()
998 .task_manager()
999 .networking_task_source()
1000 .queue(task!(terminate_module_fetch: move |cx| {
1001 finish_fetching_a_script(&trusted.root(), kind, cx);
1003 }));
1004 },
1005 );
1006 },
1007 ScriptType::ImportMap => {
1008 let import_map_result =
1011 parse_an_import_map_string(cx, global, Rc::clone(&text_rc), base_url);
1012 let script = Script::ImportMap(import_map_result);
1013
1014 self.execute(cx, Ok(script));
1016 return;
1017 },
1018 }
1019 }
1020
1021 match kind {
1023 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
1024 ExternalScriptKind::ParsingBlocking => {
1025 delayed_document.set_pending_parsing_blocking_script(self, None);
1026 },
1027 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
1028 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
1029 }
1030 }
1031
1032 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
1034 let doc = self.owner_document();
1036
1037 if *doc != *self.preparation_time_document.get().unwrap() {
1039 return;
1040 }
1041
1042 if self.marked_as_render_blocking.replace(false) {
1044 self.marked_as_render_blocking.set(false);
1045 doc.decrement_render_blocking_element_count();
1046 }
1047
1048 let script = match result {
1049 Err(_) => {
1051 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
1052 return;
1053 },
1054
1055 Ok(script) => script,
1056 };
1057
1058 let neutralized_doc =
1062 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
1063 let doc = self.owner_document();
1064 doc.incr_ignore_destructive_writes_counter();
1065 Some(doc)
1066 } else {
1067 None
1068 };
1069
1070 let document = self.owner_document();
1071
1072 match script {
1073 Script::Classic(script) => {
1074 let old_script = document.GetCurrentScript();
1076
1077 if self.upcast::<Node>().is_in_a_shadow_tree() {
1080 document.set_current_script(None)
1081 } else {
1082 document.set_current_script(Some(self))
1083 }
1084
1085 _ = self
1087 .owner_global()
1088 .run_a_classic_script(cx, script, RethrowErrors::No);
1089
1090 document.set_current_script(old_script.as_deref());
1092 },
1093 Script::Module(module_tree) => {
1094 document.set_current_script(None);
1096
1097 self.owner_global()
1099 .run_a_module_script(cx, module_tree, false);
1100 },
1101 Script::ImportMap(import_map) => {
1102 register_import_map(cx, &self.owner_global(), import_map);
1104 },
1105 }
1106
1107 if let Some(doc) = neutralized_doc {
1110 doc.decr_ignore_destructive_writes_counter();
1111 }
1112
1113 if self.from_an_external_file.get() {
1115 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1116 }
1117 }
1118
1119 pub(crate) fn queue_error_event(&self) {
1120 self.owner_global()
1121 .task_manager()
1122 .dom_manipulation_task_source()
1123 .queue_simple_event(self.upcast(), atom!("error"));
1124 }
1125
1126 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1128 let element = self.upcast::<Element>();
1129
1130 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1131 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1132
1133 match (type_attr, language_attr) {
1134 (Some(ty), _) if ty.is_empty() => {
1135 debug!("script type empty, inferring js");
1136 Some(ScriptType::Classic)
1137 },
1138 (None, Some(lang)) if lang.is_empty() => {
1139 debug!("script type empty, inferring js");
1140 Some(ScriptType::Classic)
1141 },
1142 (None, None) => {
1143 debug!("script type empty, inferring js");
1144 Some(ScriptType::Classic)
1145 },
1146 (None, Some(lang)) => {
1147 debug!("script language={}", lang);
1148 let language = format!("text/{}", lang);
1149
1150 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1151 Some(ScriptType::Classic)
1152 } else {
1153 None
1154 }
1155 },
1156 (Some(ty), _) => {
1157 debug!("script type={}", ty);
1158
1159 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1160 return Some(ScriptType::Module);
1161 }
1162
1163 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1164 return Some(ScriptType::ImportMap);
1165 }
1166
1167 if SCRIPT_JS_MIMES
1168 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1169 {
1170 Some(ScriptType::Classic)
1171 } else {
1172 None
1173 }
1174 },
1175 }
1176 }
1177
1178 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1179 self.parser_inserted.set(parser_inserted);
1180 }
1181
1182 pub(crate) fn get_parser_inserted(&self) -> bool {
1183 self.parser_inserted.get()
1184 }
1185
1186 pub(crate) fn set_already_started(&self, already_started: bool) {
1187 self.already_started.set(already_started);
1188 }
1189
1190 pub(crate) fn get_non_blocking(&self) -> bool {
1191 self.non_blocking.get()
1192 }
1193
1194 fn text(&self) -> DOMString {
1195 match self.Text() {
1196 TrustedScriptOrString::String(value) => value,
1197 TrustedScriptOrString::TrustedScript(trusted_script) => {
1198 DOMString::from(trusted_script.to_string())
1199 },
1200 }
1201 }
1202}
1203
1204impl VirtualMethods for HTMLScriptElement {
1205 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1206 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1207 }
1208
1209 fn attribute_mutated(
1210 &self,
1211 cx: &mut js::context::JSContext,
1212 attr: AttrRef<'_>,
1213 mutation: AttributeMutation,
1214 ) {
1215 self.super_type()
1216 .unwrap()
1217 .attribute_mutated(cx, attr, mutation);
1218 if *attr.local_name() == local_name!("src") {
1219 if let AttributeMutation::Set(..) = mutation &&
1220 !self.parser_inserted.get() &&
1221 self.upcast::<Node>().is_connected()
1222 {
1223 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1224 }
1225 } else if *attr.local_name() == local_name!("blocking") &&
1226 !self.has_render_blocking_attribute() &&
1227 self.marked_as_render_blocking.replace(false)
1228 {
1229 let document = self.owner_document();
1230 document.decrement_render_blocking_element_count();
1231 }
1232 }
1233
1234 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1236 if let Some(s) = self.super_type() {
1237 s.children_changed(cx, mutation);
1238 }
1239
1240 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1241 let script = DomRoot::from_ref(self);
1242 self.owner_document().add_delayed_task(
1246 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1247 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1248 }),
1249 );
1250 }
1251 }
1252
1253 fn post_connection_steps(&self, cx: &mut JSContext) {
1255 if let Some(s) = self.super_type() {
1256 s.post_connection_steps(cx);
1257 }
1258
1259 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1260 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1261 }
1262 }
1263
1264 fn cloning_steps(
1265 &self,
1266 cx: &mut JSContext,
1267 copy: &Node,
1268 maybe_doc: Option<&Document>,
1269 clone_children: CloneChildrenFlag,
1270 ) {
1271 if let Some(s) = self.super_type() {
1272 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1273 }
1274
1275 if self.already_started.get() {
1277 copy.downcast::<HTMLScriptElement>()
1278 .unwrap()
1279 .set_already_started(true);
1280 }
1281 }
1282
1283 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1284 self.super_type().unwrap().unbind_from_tree(cx, context);
1285
1286 if self.marked_as_render_blocking.replace(false) {
1287 let document = self.owner_document();
1288 document.decrement_render_blocking_element_count();
1289 }
1290 }
1291}
1292
1293impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1294 fn Src(&self) -> TrustedScriptURLOrUSVString {
1296 let element = self.upcast::<Element>();
1297 element.get_trusted_type_url_attribute(&local_name!("src"))
1298 }
1299
1300 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1302 let element = self.upcast::<Element>();
1303 let local_name = &local_name!("src");
1304 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1305 cx,
1306 &element.owner_global(),
1307 value,
1308 &format!("HTMLScriptElement {}", local_name),
1309 )?;
1310 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1311 Ok(())
1312 }
1313
1314 make_getter!(Type, "type");
1316 make_setter!(SetType, "type");
1318
1319 make_getter!(Charset, "charset");
1321 make_setter!(SetCharset, "charset");
1323
1324 fn Async(&self) -> bool {
1326 self.non_blocking.get() ||
1327 self.upcast::<Element>()
1328 .has_attribute(&local_name!("async"))
1329 }
1330
1331 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1333 self.non_blocking.set(false);
1334 self.upcast::<Element>()
1335 .set_bool_attribute(cx, &local_name!("async"), value);
1336 }
1337
1338 make_bool_getter!(Defer, "defer");
1340 make_bool_setter!(SetDefer, "defer");
1342
1343 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1345 self.blocking.or_init(|| {
1346 DOMTokenList::new(
1347 cx,
1348 self.upcast(),
1349 &local_name!("blocking"),
1350 Some(vec![Atom::from("render")]),
1351 )
1352 })
1353 }
1354
1355 make_bool_getter!(NoModule, "nomodule");
1357 make_bool_setter!(SetNoModule, "nomodule");
1359
1360 make_getter!(Integrity, "integrity");
1362 make_setter!(SetIntegrity, "integrity");
1364
1365 make_getter!(Event, "event");
1367 make_setter!(SetEvent, "event");
1369
1370 make_getter!(HtmlFor, "for");
1372 make_setter!(SetHtmlFor, "for");
1374
1375 fn GetCrossOrigin(&self) -> Option<DOMString> {
1377 reflect_cross_origin_attribute(self.upcast::<Element>())
1378 }
1379
1380 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1382 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1383 }
1384
1385 fn ReferrerPolicy(&self) -> DOMString {
1387 reflect_referrer_policy_attribute(self.upcast::<Element>())
1388 }
1389
1390 make_setter!(SetReferrerPolicy, "referrerpolicy");
1392
1393 fn InnerText(&self) -> TrustedScriptOrString {
1395 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1397 }
1398
1399 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1401 let value = TrustedScript::get_trusted_type_compliant_string(
1404 cx,
1405 &self.owner_global(),
1406 input,
1407 "HTMLScriptElement innerText",
1408 )?;
1409 *self.script_text.borrow_mut() = value.clone();
1410 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1412 Ok(())
1413 }
1414
1415 fn Text(&self) -> TrustedScriptOrString {
1417 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1418 }
1419
1420 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1422 let value = TrustedScript::get_trusted_type_compliant_string(
1425 cx,
1426 &self.owner_global(),
1427 value,
1428 "HTMLScriptElement text",
1429 )?;
1430 *self.script_text.borrow_mut() = value.clone();
1432 Node::string_replace_all(cx, value, self.upcast::<Node>());
1434 Ok(())
1435 }
1436
1437 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1439 Some(TrustedScriptOrString::String(
1441 self.upcast::<Node>().GetTextContent()?,
1442 ))
1443 }
1444
1445 fn SetTextContent(
1447 &self,
1448 cx: &mut JSContext,
1449 value: Option<TrustedScriptOrString>,
1450 ) -> Fallible<()> {
1451 let value = TrustedScript::get_trusted_type_compliant_string(
1454 cx,
1455 &self.owner_global(),
1456 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1457 "HTMLScriptElement textContent",
1458 )?;
1459 *self.script_text.borrow_mut() = value.clone();
1461 self.upcast::<Node>()
1463 .set_text_content_for_element(cx, Some(value));
1464 Ok(())
1465 }
1466
1467 fn Supports(_window: &Window, type_: DOMString) -> bool {
1469 matches!(&*type_.str(), "classic" | "module" | "importmap")
1472 }
1473}
1474
1475pub(crate) fn substitute_with_local_script(
1476 window: &Window,
1477 script: &mut Cow<'_, str>,
1478 url: ServoUrl,
1479) {
1480 if window.local_script_source().is_none() {
1481 return;
1482 }
1483 let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1484 path = path.join(&url[url::Position::BeforeHost..]);
1485 debug!("Attempting to read script stored at: {:?}", path);
1486 match read_to_string(path.clone()) {
1487 Ok(local_script) => {
1488 debug!("Found script stored at: {:?}", path);
1489 *script = Cow::Owned(local_script);
1490 },
1491 Err(why) => warn!("Could not restore script from file {:?}", why),
1492 }
1493}
1494
1495#[derive(Clone, Copy)]
1496enum ExternalScriptKind {
1497 Deferred,
1498 ParsingBlocking,
1499 AsapInOrder,
1500 Asap,
1501}