1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::borrow::Cow;
8use std::cell::Cell;
9use std::ffi::CStr;
10use std::fs::read_to_string;
11use std::path::PathBuf;
12use std::rc::Rc;
13
14use bytes::{Bytes, BytesMut};
15use dom_struct::dom_struct;
16use encoding_rs::Encoding;
17use html5ever::{LocalName, Prefix, local_name};
18use js::context::JSContext;
19use js::rust::HandleObject;
20use net_traits::blob_url_store::UrlWithBlobClaim;
21use net_traits::http_status::HttpStatus;
22use net_traits::request::{
23 CorsSettings, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId,
24};
25use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
26use script_bindings::cell::DomRefCell;
27use servo_base::id::WebViewId;
28use servo_url::ServoUrl;
29use style::attr::AttrValue;
30use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
31use stylo_atoms::Atom;
32
33use crate::dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
34use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
35use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
36use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
37use crate::dom::bindings::codegen::UnionTypes::{
38 TrustedScriptOrString, TrustedScriptURLOrUSVString,
39};
40use crate::dom::bindings::error::Fallible;
41use crate::dom::bindings::inheritance::Castable;
42use crate::dom::bindings::refcounted::Trusted;
43use crate::dom::bindings::reflector::DomGlobal;
44use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
45use crate::dom::bindings::str::DOMString;
46use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
47use crate::dom::document::Document;
48use crate::dom::domtokenlist::DOMTokenList;
49use crate::dom::element::attributes::storage::AttrRef;
50use crate::dom::element::{
51 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
52 cors_settings_attribute_credential_mode, referrer_policy_for_element,
53 reflect_cross_origin_attribute, reflect_referrer_policy_attribute, set_cross_origin_attribute,
54};
55use crate::dom::event::eventtarget::EventTarget;
56use crate::dom::globalscope::GlobalScope;
57use crate::dom::globalscope::script_execution::{ClassicScript, RethrowErrors};
58use crate::dom::html::htmlelement::HTMLElement;
59use crate::dom::node::virtualmethods::VirtualMethods;
60use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits, UnbindContext};
61use crate::dom::performance::performanceresourcetiming::InitiatorType;
62use crate::dom::script_execution::ScriptOptions;
63use crate::dom::trustedtypes::trustedscript::TrustedScript;
64use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
65use crate::dom::window::Window;
66use crate::event_loop::document_loader::{LoadBlocker, LoadType};
67use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request_with_claim};
68use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
69use crate::modules::import_map::{ImportMap, parse_an_import_map_string, register_import_map};
70use crate::modules::script_module::{
71 ModuleTree, ScriptFetchOptions, fetch_an_external_module_script, fetch_inline_module_script,
72};
73use crate::runtime::script_runtime::IntroductionType;
74use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
75
76#[dom_struct]
77pub(crate) struct HTMLScriptElement {
78 htmlelement: HTMLElement,
79
80 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
82
83 already_started: Cell<bool>,
85
86 parser_inserted: Cell<bool>,
88
89 non_blocking: Cell<bool>,
93
94 parser_document: Dom<Document>,
97
98 preparation_time_document: MutNullableDom<Document>,
101
102 line_number: u64,
104
105 script_text: DomRefCell<DOMString>,
107
108 from_an_external_file: Cell<bool>,
110
111 blocking: MutNullableDom<DOMTokenList>,
113
114 marked_as_render_blocking: Cell<bool>,
117
118 result: DomRefCell<Option<ScriptResult>>,
120}
121
122impl HTMLScriptElement {
123 fn new_inherited(
124 local_name: LocalName,
125 prefix: Option<Prefix>,
126 document: &Document,
127 creator: ElementCreator,
128 ) -> HTMLScriptElement {
129 HTMLScriptElement {
130 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
131 already_started: Cell::new(false),
132 delaying_the_load_event: Default::default(),
133 parser_inserted: Cell::new(creator.is_parser_created()),
134 non_blocking: Cell::new(!creator.is_parser_created()),
135 parser_document: Dom::from_ref(document),
136 preparation_time_document: MutNullableDom::new(None),
137 line_number: creator.return_line_number(),
138 script_text: DomRefCell::new(DOMString::new()),
139 from_an_external_file: Cell::new(false),
140 blocking: Default::default(),
141 marked_as_render_blocking: Default::default(),
142 result: DomRefCell::new(None),
143 }
144 }
145
146 pub(crate) fn new(
147 cx: &mut js::context::JSContext,
148 local_name: LocalName,
149 prefix: Option<Prefix>,
150 document: &Document,
151 proto: Option<HandleObject>,
152 creator: ElementCreator,
153 ) -> DomRoot<HTMLScriptElement> {
154 Node::reflect_node_with_proto(
155 cx,
156 Box::new(HTMLScriptElement::new_inherited(
157 local_name, prefix, document, creator,
158 )),
159 document,
160 proto,
161 )
162 }
163
164 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
169 debug_assert!(self.delaying_the_load_event.borrow().is_none());
170
171 *self.delaying_the_load_event.borrow_mut() =
172 Some(LoadBlocker::new(document, LoadType::Script(url)));
173 }
174
175 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
182 let element = self.upcast::<Element>();
183
184 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
185 ExternalScriptKind::Asap
186 } else if !self.parser_inserted.get() {
187 ExternalScriptKind::AsapInOrder
188 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
189 {
190 ExternalScriptKind::Deferred
191 } else {
192 ExternalScriptKind::ParsingBlocking
193 }
194 }
195
196 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
198 match script_kind {
199 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
200 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
201 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
202 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
203 }
204 }
205}
206
207pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
210 "application/ecmascript",
211 "application/javascript",
212 "application/x-ecmascript",
213 "application/x-javascript",
214 "text/ecmascript",
215 "text/javascript",
216 "text/javascript1.0",
217 "text/javascript1.1",
218 "text/javascript1.2",
219 "text/javascript1.3",
220 "text/javascript1.4",
221 "text/javascript1.5",
222 "text/jscript",
223 "text/livescript",
224 "text/x-ecmascript",
225 "text/x-javascript",
226];
227
228#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
229pub(crate) enum ScriptType {
230 Classic,
231 Module,
232 ImportMap,
233}
234
235fn finish_fetching_a_script(
237 elem: &HTMLScriptElement,
238 script_kind: ExternalScriptKind,
239 cx: &mut JSContext,
240) {
241 let load = elem.result.take().expect("Result must be ready to proceed");
242
243 match script_kind {
245 ExternalScriptKind::Asap => {
246 let document = elem.preparation_time_document.get().unwrap();
247 document.asap_script_loaded(cx, elem, load)
248 },
249 ExternalScriptKind::AsapInOrder => {
250 let document = elem.preparation_time_document.get().unwrap();
251 document.asap_in_order_script_loaded(cx, elem, load)
252 },
253 ExternalScriptKind::Deferred => {
254 let document = elem.parser_document.as_rooted();
255 document.deferred_script_loaded(cx, elem, load);
256 },
257 ExternalScriptKind::ParsingBlocking => {
258 let document = elem.parser_document.as_rooted();
259 document.pending_parsing_blocking_script_loaded(elem, load, cx);
260 },
261 }
262
263 LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
265}
266
267pub(crate) type ScriptResult = Result<Script, ()>;
268
269#[derive(JSTraceable, MallocSizeOf)]
271pub(crate) enum Script {
272 Classic(ClassicScript),
273 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
274 ImportMap(Fallible<ImportMap>),
275}
276
277struct ClassicContext {
279 elem: Trusted<HTMLScriptElement>,
281 kind: ExternalScriptKind,
283 character_encoding: &'static Encoding,
286 data: BytesMut,
288 metadata: Option<Metadata>,
290 url: UrlWithBlobClaim,
292 status: Result<(), NetworkError>,
294 fetch_options: ScriptFetchOptions,
296 response_was_cors_cross_origin: bool,
298}
299
300impl FetchResponseListener for ClassicContext {
301 fn process_request_body(&mut self, _: RequestId) {}
303
304 fn process_response(
305 &mut self,
306 _: &mut js::context::JSContext,
307 _: RequestId,
308 metadata: Result<FetchMetadata, NetworkError>,
309 ) {
310 self.metadata = metadata.ok().map(|meta| {
311 self.response_was_cors_cross_origin = meta.is_cors_cross_origin();
312 match meta {
313 FetchMetadata::Unfiltered(m) => m,
314 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
315 }
316 });
317
318 let status = self
319 .metadata
320 .as_ref()
321 .map(|m| m.status.clone())
322 .unwrap_or_else(HttpStatus::new_error);
323
324 self.status = {
325 if status.is_error() {
326 Err(NetworkError::ResourceLoadError(
327 "No http status code received".to_owned(),
328 ))
329 } else if status.is_success() {
330 Ok(())
331 } else {
332 Err(NetworkError::ResourceLoadError(format!(
333 "HTTP error code {}",
334 status.code()
335 )))
336 }
337 };
338 }
339
340 fn process_response_chunk(
341 &mut self,
342 _: &mut js::context::JSContext,
343 _: RequestId,
344 chunk: Bytes,
345 ) {
346 if self.status.is_ok() {
347 self.data.extend_from_slice(&chunk);
348 }
349 }
350
351 fn process_response_eof(
354 mut self,
355 cx: &mut js::context::JSContext,
356 _: RequestId,
357 response: Result<(), NetworkError>,
358 timing: ResourceFetchTiming,
359 ) {
360 network_listener::submit_timing(cx, &self, &response, &timing);
362
363 let elem = self.elem.root();
364
365 match (response.as_ref(), self.status.as_ref()) {
366 (Err(error), _) | (_, Err(error)) => {
367 error!(
368 "Fetching classic script failed {:?} ({:?})",
369 error, self.url
370 );
371 *elem.result.borrow_mut() = Some(Err(()));
373 finish_fetching_a_script(&elem, self.kind, cx);
374 return;
375 },
376 _ => {},
377 };
378
379 let metadata = self.metadata.take().unwrap();
380 let final_url = metadata.final_url;
381
382 let encoding = metadata
385 .charset
386 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
387 .unwrap_or(self.character_encoding);
388
389 let (mut source_text, _, _) = encoding.decode(&self.data);
391
392 let global = elem.global();
393
394 if let Some(window) = global.downcast::<Window>() &&
395 let Some(script_source) = window.local_script_source()
396 {
397 substitute_with_local_script(script_source, &mut source_text, &final_url);
398 }
399
400 let mut script_options = ScriptOptions::External;
402 script_options.set(
403 ScriptOptions::MutedErrors,
404 self.response_was_cors_cross_origin,
405 );
406
407 let script = global.create_a_classic_script(
410 cx,
411 source_text,
412 final_url,
413 script_options,
414 self.fetch_options.clone(),
415 Some(IntroductionType::SRC_SCRIPT),
416 1,
417 );
418
419 *elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
450 finish_fetching_a_script(&elem, self.kind, cx);
451 }
453
454 fn process_csp_violations(
455 &mut self,
456 cx: &mut js::context::JSContext,
457 _request_id: RequestId,
458 violations: Vec<Violation>,
459 ) {
460 let global = &self.resource_timing_global();
461 let elem = self.elem.root();
462 global.report_csp_violations(cx, violations, Some(elem.upcast()), None);
463 }
464
465 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
466 self.data.reserve(size.saturating_sub(self.data.len()));
467 }
468}
469
470impl ResourceTimingListener for ClassicContext {
471 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
472 let initiator_type = InitiatorType::LocalName(
473 self.elem
474 .root()
475 .upcast::<Element>()
476 .local_name()
477 .to_string(),
478 );
479 (initiator_type, self.url.url())
480 }
481
482 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
483 self.elem.root().owner_document().global()
484 }
485}
486
487#[allow(clippy::too_many_arguments)]
490pub(crate) fn script_fetch_request(
491 webview_id: WebViewId,
492 url: UrlWithBlobClaim,
493 cors_setting: Option<CorsSettings>,
494 options: ScriptFetchOptions,
495 referrer: Referrer,
496) -> RequestBuilder {
497 create_a_potential_cors_request_with_claim(
500 Some(webview_id),
501 url,
502 Destination::Script,
503 cors_setting,
504 None,
505 referrer,
506 )
507 .parser_metadata(options.parser_metadata)
508 .integrity_metadata(options.integrity_metadata.clone())
509 .referrer_policy(options.referrer_policy)
510 .cryptographic_nonce_metadata(options.cryptographic_nonce)
511}
512
513fn fetch_a_classic_script(
515 script: &HTMLScriptElement,
516 kind: ExternalScriptKind,
517 url: UrlWithBlobClaim,
518 cors_setting: Option<CorsSettings>,
519 options: ScriptFetchOptions,
520 character_encoding: &'static Encoding,
521) {
522 let doc = script.owner_document();
524 let global = script.global();
525 let referrer = global.get_referrer();
526 let request = script_fetch_request(
527 doc.webview_id(),
528 url.clone(),
529 cors_setting,
530 options.clone(),
531 referrer,
532 )
533 .with_global_scope(&global);
534
535 let context = ClassicContext {
538 elem: Trusted::new(script),
539 kind,
540 character_encoding,
541 data: BytesMut::new(),
542 metadata: None,
543 url,
544 status: Ok(()),
545 fetch_options: options,
546 response_was_cors_cross_origin: false,
547 };
548 doc.fetch_background(request, context);
549}
550
551impl HTMLScriptElement {
552 pub(crate) fn set_initial_script_text(&self) {
554 *self.script_text.borrow_mut() = self.text();
555 }
556
557 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
559 if *self.script_text.borrow() != self.text() {
563 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
564 cx,
565 &self.owner_global(),
566 self.Text(),
567 "HTMLScriptElement text",
568 )?;
569 }
570
571 Ok(())
572 }
573
574 fn has_render_blocking_attribute(&self) -> bool {
575 self.blocking
576 .get()
577 .is_some_and(|list| list.Contains(DOMString::from_static("render")))
578 }
579
580 fn potentially_render_blocking(&self) -> bool {
582 if self.has_render_blocking_attribute() {
586 return true;
587 }
588 let element = self.upcast::<Element>();
589 self.get_script_type()
593 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
594 self.parser_inserted.get() &&
595 !element.has_attribute(&local_name!("async")) &&
596 !element.has_attribute(&local_name!("defer"))
597 }
598
599 pub(crate) fn prepare(
601 &self,
602 cx: &mut JSContext,
603 introduction_type_override: Option<&'static CStr>,
604 ) {
605 let introduction_type =
606 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
607
608 if self.already_started.get() {
610 return;
611 }
612
613 let was_parser_inserted = self.parser_inserted.get();
618 self.parser_inserted.set(false);
619
620 let element = self.upcast::<Element>();
623 let asynch = element.has_attribute(&local_name!("async"));
624 if was_parser_inserted && !asynch {
626 self.non_blocking.set(true);
627 }
628
629 if self.prepare_the_script_text(cx).is_err() {
632 return;
633 }
634 let text: Cow<'_, str> = Cow::Owned(String::from(self.script_text.borrow().str()));
636 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
638 return;
639 }
640
641 if !self.upcast::<Node>().is_connected() {
643 return;
644 }
645
646 let script_type = if let Some(ty) = self.get_script_type() {
647 ty
649 } else {
650 return;
652 };
653
654 if was_parser_inserted {
658 self.parser_inserted.set(true);
659 self.non_blocking.set(false);
660 }
661
662 self.already_started.set(true);
664
665 let doc = self.owner_document();
667 self.preparation_time_document.set(Some(&doc));
668
669 if self.parser_inserted.get() && *self.parser_document != *doc {
673 return;
674 }
675
676 if !doc.scripting_enabled() {
678 return;
679 }
680
681 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
683 return;
684 }
685
686 let global = &doc.global();
687
688 if !element.has_attribute(&local_name!("src")) &&
690 global
691 .get_csp_list()
692 .should_elements_inline_type_behavior_be_blocked(
693 cx,
694 global,
695 element,
696 InlineCheckType::Script,
697 &text,
698 self.line_number as u32,
699 )
700 {
701 warn!("Blocking inline script due to CSP");
702 return;
703 }
704
705 if script_type == ScriptType::Classic {
707 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
708 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
709 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
710 let for_value = for_attribute.to_ascii_lowercase();
711 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
712 if for_value != "window" {
713 return;
714 }
715
716 let event_value = event_attribute.to_ascii_lowercase();
717 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
718 if event_value != "onload" && event_value != "onload()" {
719 return;
720 }
721 }
722 }
723
724 let encoding = element
729 .get_attribute_string_value(&local_name!("charset"))
730 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
731 .unwrap_or_else(|| doc.encoding());
732
733 let cors_setting = cors_setting_for_element(element);
735
736 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
738
739 let cryptographic_nonce =
744 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
745 element.nonce_value().trim().to_owned()
746 } else {
747 String::new()
748 };
749
750 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
753 let integrity_val_is_none = integrity_val.is_none();
754 let integrity_metadata = integrity_val.unwrap_or_default();
755
756 let referrer_policy = referrer_policy_for_element(element);
758
759 let parser_metadata = if self.parser_inserted.get() {
764 ParserMetadata::ParserInserted
765 } else {
766 ParserMetadata::NotParserInserted
767 };
768
769 let mut script_fetch_options = ScriptFetchOptions {
771 cryptographic_nonce,
772 integrity_metadata,
773 parser_metadata,
774 referrer_policy,
775 credentials_mode: module_credentials_mode,
776 render_blocking: false,
777 };
778
779 let base_url = doc.base_url();
782
783 let kind = self.get_script_kind(script_type);
784 let delayed_document = self.get_script_active_document(kind);
785
786 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
789 if script_type == ScriptType::ImportMap {
791 self.queue_error_event();
794 return;
795 }
796
797 if src.is_empty() {
799 self.queue_error_event();
800 return;
801 }
802
803 self.from_an_external_file.set(true);
805
806 let url = match base_url.join(&src) {
808 Ok(url) => url,
809 Err(_) => {
810 warn!("error parsing URL for script {}", src);
811 self.queue_error_event();
812 return;
813 },
814 };
815 let url = ensure_blob_referenced_by_url_is_kept_alive(global, url);
816
817 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
819 self.marked_as_render_blocking.set(true);
820 doc.increment_render_blocking_element_count();
821 }
822
823 self.delay_load_event(&delayed_document, url.url());
825
826 if self.marked_as_render_blocking.get() {
828 script_fetch_options.render_blocking = true;
829 }
830
831 match script_type {
833 ScriptType::Classic => {
834 fetch_a_classic_script(
836 self,
837 kind,
838 url,
839 cors_setting,
840 script_fetch_options,
841 encoding,
842 );
843 },
844 ScriptType::Module => {
845 if integrity_val_is_none {
848 script_fetch_options.integrity_metadata = global
849 .import_map()
850 .resolve_a_module_integrity_metadata(&url.url());
851 }
852
853 let script = DomRoot::from_ref(self);
854
855 fetch_an_external_module_script(
857 cx,
858 url,
859 global,
860 script_fetch_options,
861 move |cx, module_tree| {
862 let load = module_tree.map(Script::Module).ok_or(());
863 *script.result.borrow_mut() = Some(load);
864
865 finish_fetching_a_script(&script, kind, cx);
866 },
867 );
868 },
869 ScriptType::ImportMap => (),
870 }
871 } else {
872 assert!(!text.is_empty());
875
876 match script_type {
878 ScriptType::Classic => {
879 let script = self.global().create_a_classic_script(
882 cx,
883 text,
884 base_url,
885 ScriptOptions::empty(),
886 script_fetch_options,
887 introduction_type,
888 self.line_number as u32,
889 );
890 let result = Ok(Script::Classic(script));
891
892 if was_parser_inserted &&
893 doc.get_current_parser()
894 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
895 doc.has_a_stylesheet_that_is_blocking_scripts()
896 {
897 doc.set_pending_parsing_blocking_script(self, Some(result));
899 } else {
900 self.execute(cx, result);
902 }
903 return;
904 },
905 ScriptType::Module => {
906 self.delay_load_event(&delayed_document, base_url.clone());
908
909 if self.potentially_render_blocking() &&
911 doc.allows_adding_render_blocking_elements()
912 {
913 self.marked_as_render_blocking.set(true);
915 doc.increment_render_blocking_element_count();
916
917 script_fetch_options.render_blocking = true;
919 }
920
921 let script = DomRoot::from_ref(self);
922 fetch_inline_module_script(
925 cx,
926 global,
927 text,
928 base_url,
929 script_fetch_options,
930 self.line_number as u32,
931 introduction_type,
932 move |_, module_tree| {
933 let load = module_tree.map(Script::Module).ok_or(());
934 *script.result.borrow_mut() = Some(load);
935
936 let trusted = Trusted::new(&*script);
937
938 script
940 .owner_global()
941 .task_manager()
942 .networking_task_source()
943 .queue(task!(terminate_module_fetch: move |cx| {
944 finish_fetching_a_script(&trusted.root(), kind, cx);
946 }));
947 },
948 );
949 },
950 ScriptType::ImportMap => {
951 let import_map_result = parse_an_import_map_string(cx, global, &text, base_url);
954 let script = Script::ImportMap(import_map_result);
955
956 self.execute(cx, Ok(script));
958 return;
959 },
960 }
961 }
962
963 match kind {
965 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
966 ExternalScriptKind::ParsingBlocking => {
967 delayed_document.set_pending_parsing_blocking_script(self, None);
968 },
969 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
970 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
971 }
972 }
973
974 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
976 let doc = self.owner_document();
978
979 if *doc != *self.preparation_time_document.get().unwrap() {
981 return;
982 }
983
984 if self.marked_as_render_blocking.replace(false) {
986 self.marked_as_render_blocking.set(false);
987 doc.decrement_render_blocking_element_count();
988 }
989
990 let script = match result {
991 Err(_) => {
993 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
994 return;
995 },
996
997 Ok(script) => script,
998 };
999
1000 let neutralized_doc =
1004 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
1005 let doc = self.owner_document();
1006 doc.incr_ignore_destructive_writes_counter();
1007 Some(doc)
1008 } else {
1009 None
1010 };
1011
1012 let document = self.owner_document();
1013
1014 match script {
1015 Script::Classic(script) => {
1016 let old_script = document.GetCurrentScript();
1018
1019 if self.upcast::<Node>().is_in_a_shadow_tree() {
1022 document.set_current_script(None)
1023 } else {
1024 document.set_current_script(Some(self))
1025 }
1026
1027 _ = self.owner_global().run_a_classic_script(
1029 cx,
1030 script,
1031 RethrowErrors::No,
1032 None, );
1034
1035 document.set_current_script(old_script.as_deref());
1037 },
1038 Script::Module(module_tree) => {
1039 document.set_current_script(None);
1041
1042 self.owner_global()
1044 .run_a_module_script(cx, module_tree, false);
1045 },
1046 Script::ImportMap(import_map) => {
1047 register_import_map(cx, &self.owner_global(), import_map);
1049 },
1050 }
1051
1052 if let Some(doc) = neutralized_doc {
1055 doc.decr_ignore_destructive_writes_counter();
1056 }
1057
1058 if self.from_an_external_file.get() {
1060 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1061 }
1062 }
1063
1064 pub(crate) fn queue_error_event(&self) {
1065 self.owner_global()
1066 .task_manager()
1067 .dom_manipulation_task_source()
1068 .queue_simple_event(self.upcast(), atom!("error"));
1069 }
1070
1071 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1073 let element = self.upcast::<Element>();
1074
1075 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1076 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1077
1078 match (type_attr, language_attr) {
1079 (Some(ty), _) if ty.is_empty() => {
1080 debug!("script type empty, inferring js");
1081 Some(ScriptType::Classic)
1082 },
1083 (None, Some(lang)) if lang.is_empty() => {
1084 debug!("script type empty, inferring js");
1085 Some(ScriptType::Classic)
1086 },
1087 (None, None) => {
1088 debug!("script type empty, inferring js");
1089 Some(ScriptType::Classic)
1090 },
1091 (None, Some(lang)) => {
1092 debug!("script language={}", lang);
1093 let language = format!("text/{}", lang);
1094
1095 if SCRIPT_JS_MIMES
1096 .iter()
1097 .any(|mime| language.eq_ignore_ascii_case(mime))
1098 {
1099 Some(ScriptType::Classic)
1100 } else {
1101 None
1102 }
1103 },
1104 (Some(ty), _) => {
1105 debug!("script type={}", ty);
1106
1107 if ty
1108 .trim_matches(HTML_SPACE_CHARACTERS)
1109 .eq_ignore_ascii_case("module")
1110 {
1111 return Some(ScriptType::Module);
1112 }
1113
1114 if ty
1115 .trim_matches(HTML_SPACE_CHARACTERS)
1116 .eq_ignore_ascii_case("importmap")
1117 {
1118 return Some(ScriptType::ImportMap);
1119 }
1120
1121 if SCRIPT_JS_MIMES.iter().any(|mime| {
1122 ty.trim_matches(HTML_SPACE_CHARACTERS)
1123 .eq_ignore_ascii_case(mime)
1124 }) {
1125 Some(ScriptType::Classic)
1126 } else {
1127 None
1128 }
1129 },
1130 }
1131 }
1132
1133 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1134 self.parser_inserted.set(parser_inserted);
1135 }
1136
1137 pub(crate) fn set_already_started(&self, already_started: bool) {
1138 self.already_started.set(already_started);
1139 }
1140
1141 fn text(&self) -> DOMString {
1142 match self.Text() {
1143 TrustedScriptOrString::String(value) => value,
1144 TrustedScriptOrString::TrustedScript(trusted_script) => {
1145 DOMString::from(trusted_script.to_string())
1146 },
1147 }
1148 }
1149}
1150
1151impl VirtualMethods for HTMLScriptElement {
1152 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1153 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1154 }
1155
1156 fn attribute_mutated(
1157 &self,
1158 cx: &mut js::context::JSContext,
1159 attr: AttrRef<'_>,
1160 mutation: AttributeMutation,
1161 ) {
1162 self.super_type()
1163 .unwrap()
1164 .attribute_mutated(cx, attr, mutation);
1165 if *attr.local_name() == local_name!("src") {
1166 if let AttributeMutation::Set(..) = mutation &&
1167 !self.parser_inserted.get() &&
1168 self.upcast::<Node>().is_connected()
1169 {
1170 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1171 }
1172 } else if *attr.local_name() == local_name!("blocking") &&
1173 !self.has_render_blocking_attribute() &&
1174 self.marked_as_render_blocking.replace(false)
1175 {
1176 let document = self.owner_document();
1177 document.decrement_render_blocking_element_count();
1178 }
1179 }
1180
1181 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1183 if let Some(s) = self.super_type() {
1184 s.children_changed(cx, mutation);
1185 }
1186
1187 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1188 let script = DomRoot::from_ref(self);
1189 self.owner_document().add_delayed_task(
1193 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1194 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1195 }),
1196 );
1197 }
1198 }
1199
1200 fn post_connection_steps(&self, cx: &mut JSContext) {
1202 if let Some(s) = self.super_type() {
1203 s.post_connection_steps(cx);
1204 }
1205
1206 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1207 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1208 }
1209 }
1210
1211 fn cloning_steps(
1212 &self,
1213 cx: &mut JSContext,
1214 copy: &Node,
1215 maybe_doc: Option<&Document>,
1216 clone_children: CloneChildrenFlag,
1217 ) {
1218 if let Some(s) = self.super_type() {
1219 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1220 }
1221
1222 if self.already_started.get() {
1224 copy.downcast::<HTMLScriptElement>()
1225 .unwrap()
1226 .set_already_started(true);
1227 }
1228 }
1229
1230 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
1231 self.super_type().unwrap().unbind_from_tree(cx, context);
1232
1233 if self.marked_as_render_blocking.replace(false) {
1234 let document = self.owner_document();
1235 document.decrement_render_blocking_element_count();
1236 }
1237 }
1238}
1239
1240impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1241 fn Src(&self) -> TrustedScriptURLOrUSVString {
1243 let element = self.upcast::<Element>();
1244 element.get_trusted_type_url_attribute(&local_name!("src"))
1245 }
1246
1247 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1249 let element = self.upcast::<Element>();
1250 let local_name = &local_name!("src");
1251 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1252 cx,
1253 &element.owner_global(),
1254 value,
1255 &format!("HTMLScriptElement {}", local_name),
1256 )?;
1257 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1258 Ok(())
1259 }
1260
1261 make_getter!(Type, "type");
1263 make_setter!(SetType, "type");
1265
1266 make_getter!(Charset, "charset");
1268 make_setter!(SetCharset, "charset");
1270
1271 fn Async(&self) -> bool {
1273 self.non_blocking.get() ||
1274 self.upcast::<Element>()
1275 .has_attribute(&local_name!("async"))
1276 }
1277
1278 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1280 self.non_blocking.set(false);
1281 self.upcast::<Element>()
1282 .set_bool_attribute(cx, &local_name!("async"), value);
1283 }
1284
1285 make_bool_getter!(Defer, "defer");
1287 make_bool_setter!(SetDefer, "defer");
1289
1290 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1292 self.blocking.or_init(|| {
1293 DOMTokenList::new(
1294 cx,
1295 self.upcast(),
1296 &local_name!("blocking"),
1297 Some(vec![Atom::from("render")]),
1298 )
1299 })
1300 }
1301
1302 make_bool_getter!(NoModule, "nomodule");
1304 make_bool_setter!(SetNoModule, "nomodule");
1306
1307 make_getter!(Integrity, "integrity");
1309 make_setter!(SetIntegrity, "integrity");
1311
1312 make_getter!(Event, "event");
1314 make_setter!(SetEvent, "event");
1316
1317 make_getter!(HtmlFor, "for");
1319 make_setter!(SetHtmlFor, "for");
1321
1322 fn GetCrossOrigin(&self) -> Option<DOMString> {
1324 reflect_cross_origin_attribute(self.upcast::<Element>())
1325 }
1326
1327 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1329 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1330 }
1331
1332 fn ReferrerPolicy(&self) -> DOMString {
1334 reflect_referrer_policy_attribute(self.upcast::<Element>())
1335 }
1336
1337 make_setter!(SetReferrerPolicy, "referrerpolicy");
1339
1340 fn InnerText(&self) -> TrustedScriptOrString {
1342 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1344 }
1345
1346 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1348 let value = TrustedScript::get_trusted_type_compliant_string(
1351 cx,
1352 &self.owner_global(),
1353 input,
1354 "HTMLScriptElement innerText",
1355 )?;
1356 *self.script_text.borrow_mut() = value.clone();
1357 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1359 Ok(())
1360 }
1361
1362 fn Text(&self) -> TrustedScriptOrString {
1364 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1365 }
1366
1367 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1369 let value = TrustedScript::get_trusted_type_compliant_string(
1372 cx,
1373 &self.owner_global(),
1374 value,
1375 "HTMLScriptElement text",
1376 )?;
1377 *self.script_text.borrow_mut() = value.clone();
1379 Node::string_replace_all(cx, value, self.upcast::<Node>());
1381 Ok(())
1382 }
1383
1384 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1386 Some(TrustedScriptOrString::String(
1388 self.upcast::<Node>().GetTextContent()?,
1389 ))
1390 }
1391
1392 fn SetTextContent(
1394 &self,
1395 cx: &mut JSContext,
1396 value: Option<TrustedScriptOrString>,
1397 ) -> Fallible<()> {
1398 let value = TrustedScript::get_trusted_type_compliant_string(
1401 cx,
1402 &self.owner_global(),
1403 value.unwrap_or(TrustedScriptOrString::String(DOMString::new())),
1404 "HTMLScriptElement textContent",
1405 )?;
1406 *self.script_text.borrow_mut() = value.clone();
1408 self.upcast::<Node>()
1410 .set_text_content_for_element(cx, Some(value));
1411 Ok(())
1412 }
1413
1414 fn Supports(_window: &Window, type_: DOMString) -> bool {
1416 matches!(&*type_.str(), "classic" | "module" | "importmap")
1419 }
1420}
1421
1422pub fn substitute_with_local_script(
1423 script_source: &str,
1424 script: &mut Cow<'_, str>,
1425 url: &ServoUrl,
1426) {
1427 let mut path = PathBuf::from(script_source);
1428 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1429 debug!("Attempting to read script stored at: {:?}", path);
1430 match read_to_string(path.clone()) {
1431 Ok(local_script) => {
1432 debug!("Found script stored at: {:?}", path);
1433 *script = Cow::Owned(local_script);
1434 },
1435 Err(why) => warn!("Could not restore script from file {:?}", why),
1436 }
1437}
1438
1439#[derive(Clone, Copy)]
1440enum ExternalScriptKind {
1441 Deferred,
1442 ParsingBlocking,
1443 AsapInOrder,
1444 Asap,
1445}