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 bytes::{Bytes, BytesMut};
13use dom_struct::dom_struct;
14use encoding_rs::Encoding;
15use html5ever::{LocalName, Prefix, local_name};
16use js::context::JSContext;
17use js::rust::HandleObject;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::http_status::HttpStatus;
20use net_traits::request::{
21 CorsSettings, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId,
22};
23use net_traits::{FetchMetadata, Metadata, NetworkError, ResourceFetchTiming};
24use script_bindings::cell::DomRefCell;
25use servo_base::id::WebViewId;
26use servo_url::ServoUrl;
27use style::attr::AttrValue;
28use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
29use stylo_atoms::Atom;
30
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::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::globalscope::GlobalScope;
55use crate::dom::globalscope::script_execution::{ClassicScript, RethrowErrors};
56use crate::dom::html::htmlelement::HTMLElement;
57use crate::dom::node::virtualmethods::VirtualMethods;
58use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits, UnbindContext};
59use crate::dom::performance::performanceresourcetiming::InitiatorType;
60use crate::dom::script_execution::ScriptOptions;
61use crate::dom::trustedtypes::trustedscript::TrustedScript;
62use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
63use crate::dom::window::Window;
64use crate::event_loop::document_loader::{LoadBlocker, LoadType};
65use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request_with_claim};
66use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
67use crate::modules::import_map::{ImportMap, parse_an_import_map_string, register_import_map};
68use crate::modules::script_module::{
69 ModuleTree, ScriptFetchOptions, fetch_an_external_module_script, fetch_inline_module_script,
70};
71use crate::runtime::script_runtime::IntroductionType;
72use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
73
74#[dom_struct]
75pub(crate) struct HTMLScriptElement {
76 htmlelement: HTMLElement,
77
78 delaying_the_load_event: DomRefCell<Option<LoadBlocker>>,
80
81 already_started: Cell<bool>,
83
84 parser_inserted: Cell<bool>,
86
87 non_blocking: Cell<bool>,
91
92 parser_document: Dom<Document>,
95
96 preparation_time_document: MutNullableDom<Document>,
99
100 line_number: u64,
102
103 script_text: DomRefCell<DOMString>,
105
106 from_an_external_file: Cell<bool>,
108
109 blocking: MutNullableDom<DOMTokenList>,
111
112 marked_as_render_blocking: Cell<bool>,
115
116 result: DomRefCell<Option<Box<ScriptResult>>>,
118}
119
120impl HTMLScriptElement {
121 fn new_inherited(
122 local_name: LocalName,
123 prefix: Option<Prefix>,
124 document: &Document,
125 creator: ElementCreator,
126 ) -> HTMLScriptElement {
127 HTMLScriptElement {
128 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
129 already_started: Cell::new(false),
130 delaying_the_load_event: Default::default(),
131 parser_inserted: Cell::new(creator.is_parser_created()),
132 non_blocking: Cell::new(!creator.is_parser_created()),
133 parser_document: Dom::from_ref(document),
134 preparation_time_document: MutNullableDom::new(None),
135 line_number: creator.return_line_number(),
136 script_text: DomRefCell::new(DOMString::new()),
137 from_an_external_file: Cell::new(false),
138 blocking: Default::default(),
139 marked_as_render_blocking: Default::default(),
140 result: DomRefCell::new(None),
141 }
142 }
143
144 pub(crate) fn new(
145 cx: &mut JSContext,
146 local_name: LocalName,
147 prefix: Option<Prefix>,
148 document: &Document,
149 proto: Option<HandleObject>,
150 creator: ElementCreator,
151 ) -> DomRoot<HTMLScriptElement> {
152 Node::reflect_node_with_proto(
153 cx,
154 Box::new(HTMLScriptElement::new_inherited(
155 local_name, prefix, document, creator,
156 )),
157 document,
158 proto,
159 )
160 }
161
162 fn delay_load_event(&self, document: &Document, url: ServoUrl) {
167 debug_assert!(self.delaying_the_load_event.borrow().is_none());
168
169 *self.delaying_the_load_event.borrow_mut() =
170 Some(LoadBlocker::new(document, LoadType::Script(url)));
171 }
172
173 fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
180 let element = self.upcast::<Element>();
181
182 if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
183 ExternalScriptKind::Asap
184 } else if !self.parser_inserted.get() {
185 ExternalScriptKind::AsapInOrder
186 } else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
187 {
188 ExternalScriptKind::Deferred
189 } else {
190 ExternalScriptKind::ParsingBlocking
191 }
192 }
193
194 fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
196 match script_kind {
197 ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
198 ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
199 ExternalScriptKind::Deferred => self.parser_document.as_rooted(),
200 ExternalScriptKind::ParsingBlocking => self.parser_document.as_rooted(),
201 }
202 }
203
204 fn finish_fetching_a_script(&self, cx: &mut JSContext, script_kind: ExternalScriptKind) {
206 let load = self.result.take().expect("Result must be ready to proceed");
207
208 match script_kind {
210 ExternalScriptKind::Asap => {
211 let document = self.preparation_time_document.get().unwrap();
212 document.asap_script_loaded(cx, self, *load)
213 },
214 ExternalScriptKind::AsapInOrder => {
215 let document = self.preparation_time_document.get().unwrap();
216 document.asap_in_order_script_loaded(cx, self, *load)
217 },
218 ExternalScriptKind::Deferred => {
219 let document = self.parser_document.as_rooted();
220 document.deferred_script_loaded(cx, self, *load);
221 },
222 ExternalScriptKind::ParsingBlocking => {
223 let document = self.parser_document.as_rooted();
224 document.pending_parsing_blocking_script_loaded(self, *load, cx);
225 },
226 }
227
228 LoadBlocker::terminate(&self.delaying_the_load_event, cx);
230 }
231}
232
233pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
236 "application/ecmascript",
237 "application/javascript",
238 "application/x-ecmascript",
239 "application/x-javascript",
240 "text/ecmascript",
241 "text/javascript",
242 "text/javascript1.0",
243 "text/javascript1.1",
244 "text/javascript1.2",
245 "text/javascript1.3",
246 "text/javascript1.4",
247 "text/javascript1.5",
248 "text/jscript",
249 "text/livescript",
250 "text/x-ecmascript",
251 "text/x-javascript",
252];
253
254#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
255pub(crate) enum ScriptType {
256 Classic,
257 Module,
258 ImportMap,
259}
260
261pub(crate) type ScriptResult = Result<Script, ()>;
262
263#[derive(JSTraceable, MallocSizeOf)]
265pub(crate) enum Script {
266 Classic(ClassicScript),
267 Module(#[conditional_malloc_size_of] Rc<ModuleTree>),
268 ImportMap(Fallible<ImportMap>),
269}
270
271struct ClassicContext {
273 elem: Trusted<HTMLScriptElement>,
275 kind: ExternalScriptKind,
277 character_encoding: &'static Encoding,
280 data: BytesMut,
282 metadata: Option<Metadata>,
284 url: UrlWithBlobClaim,
286 status: Result<(), NetworkError>,
288 fetch_options: ScriptFetchOptions,
290 response_was_cors_cross_origin: bool,
292}
293
294impl FetchResponseListener for ClassicContext {
295 fn process_request_body(&mut self, _: RequestId) {}
297
298 fn process_response(
299 &mut self,
300 _: &mut JSContext,
301 _: RequestId,
302 metadata: Result<FetchMetadata, NetworkError>,
303 ) {
304 self.metadata = metadata.ok().map(|metadata| {
305 self.response_was_cors_cross_origin = metadata.is_cors_cross_origin();
306 metadata.into()
307 });
308
309 let status = self
310 .metadata
311 .as_ref()
312 .map(|m| m.status.clone())
313 .unwrap_or_else(HttpStatus::new_error);
314
315 self.status = {
316 if status.is_error() {
317 Err(NetworkError::ResourceLoadError(
318 "No http status code received".to_owned(),
319 ))
320 } else if status.is_success() {
321 Ok(())
322 } else {
323 Err(NetworkError::ResourceLoadError(format!(
324 "HTTP error code {}",
325 status.code()
326 )))
327 }
328 };
329 }
330
331 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Bytes) {
332 if self.status.is_ok() {
333 self.data.extend_from_slice(&chunk);
334 }
335 }
336
337 fn process_response_eof(
340 mut self,
341 cx: &mut JSContext,
342 _: RequestId,
343 response: Result<(), NetworkError>,
344 timing: ResourceFetchTiming,
345 ) {
346 network_listener::submit_timing(cx, &self, &response, &timing);
348
349 let elem = self.elem.root();
350
351 match (response.as_ref(), self.status.as_ref()) {
352 (Err(error), _) | (_, Err(error)) => {
353 error!(
354 "Fetching classic script failed {:?} ({:?})",
355 error, self.url
356 );
357 *elem.result.borrow_mut() = Some(Box::new(Err(())));
359 elem.finish_fetching_a_script(cx, self.kind);
360 return;
361 },
362 _ => {},
363 };
364
365 let metadata = self.metadata.take().unwrap();
366 let final_url = metadata.final_url;
367
368 let encoding = metadata
371 .charset
372 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
373 .unwrap_or(self.character_encoding);
374
375 let (mut source_text, _, _) = encoding.decode(&self.data);
377
378 let global = elem.global();
379
380 if let Some(window) = global.downcast::<Window>() &&
381 let Some(script_source) = window.local_script_source()
382 {
383 substitute_with_local_script(script_source, &mut source_text, &final_url);
384 }
385
386 let mut script_options = ScriptOptions::External;
388 script_options.set(
389 ScriptOptions::MutedErrors,
390 self.response_was_cors_cross_origin,
391 );
392
393 let script = global.create_a_classic_script(
396 cx,
397 source_text,
398 final_url,
399 script_options,
400 self.fetch_options.clone(),
401 Some(IntroductionType::SRC_SCRIPT),
402 1,
403 );
404
405 *elem.result.borrow_mut() = Some(Box::new(Ok(Script::Classic(script))));
436 elem.finish_fetching_a_script(cx, self.kind);
437 }
439
440 fn process_csp_violations(
441 &mut self,
442 cx: &mut JSContext,
443 _request_id: RequestId,
444 violations: Vec<Violation>,
445 ) {
446 let global = &self.resource_timing_global();
447 let elem = self.elem.root();
448 global.report_csp_violations(cx, violations, Some(elem.upcast()), None);
449 }
450
451 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
452 self.data.reserve(size.saturating_sub(self.data.len()));
453 }
454}
455
456impl ResourceTimingListener for ClassicContext {
457 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
458 let initiator_type = InitiatorType::LocalName(
459 self.elem
460 .root()
461 .upcast::<Element>()
462 .local_name()
463 .to_string(),
464 );
465 (initiator_type, self.url.url())
466 }
467
468 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
469 self.elem.root().owner_document().global()
470 }
471}
472
473#[allow(clippy::too_many_arguments)]
476pub(crate) fn script_fetch_request(
477 webview_id: WebViewId,
478 url: UrlWithBlobClaim,
479 cors_setting: Option<CorsSettings>,
480 options: ScriptFetchOptions,
481 referrer: Referrer,
482) -> RequestBuilder {
483 create_a_potential_cors_request_with_claim(
486 Some(webview_id),
487 url,
488 Destination::Script,
489 cors_setting,
490 None,
491 referrer,
492 )
493 .parser_metadata(options.parser_metadata)
494 .integrity_metadata(options.integrity_metadata.clone())
495 .referrer_policy(options.referrer_policy)
496 .cryptographic_nonce_metadata(options.cryptographic_nonce)
497}
498
499fn fetch_a_classic_script(
501 script: &HTMLScriptElement,
502 kind: ExternalScriptKind,
503 url: UrlWithBlobClaim,
504 cors_setting: Option<CorsSettings>,
505 options: ScriptFetchOptions,
506 character_encoding: &'static Encoding,
507) {
508 let doc = script.owner_document();
510 let global = script.global();
511 let referrer = global.get_referrer();
512 let request = script_fetch_request(
513 doc.webview_id(),
514 url.clone(),
515 cors_setting,
516 options.clone(),
517 referrer,
518 )
519 .with_global_scope(&global);
520
521 let context = ClassicContext {
524 elem: Trusted::new(script),
525 kind,
526 character_encoding,
527 data: BytesMut::new(),
528 metadata: None,
529 url,
530 status: Ok(()),
531 fetch_options: options,
532 response_was_cors_cross_origin: false,
533 };
534 doc.fetch_background(request, context);
535}
536
537impl HTMLScriptElement {
538 pub(crate) fn set_initial_script_text(&self) {
540 *self.script_text.borrow_mut() = self.text();
541 }
542
543 fn prepare_the_script_text(&self, cx: &mut JSContext) -> Fallible<()> {
545 if *self.script_text.borrow() != self.text() {
549 *self.script_text.borrow_mut() = TrustedScript::get_trusted_type_compliant_string(
550 cx,
551 &self.owner_global(),
552 self.Text(),
553 "HTMLScriptElement text",
554 )?;
555 }
556
557 Ok(())
558 }
559
560 fn has_render_blocking_attribute(&self) -> bool {
561 self.blocking
562 .get()
563 .is_some_and(|list| list.Contains(DOMString::from_static("render")))
564 }
565
566 fn potentially_render_blocking(&self) -> bool {
568 if self.has_render_blocking_attribute() {
572 return true;
573 }
574 let element = self.upcast::<Element>();
575 self.get_script_type()
579 .is_some_and(|script_type| script_type == ScriptType::Classic) &&
580 self.parser_inserted.get() &&
581 !element.has_attribute(&local_name!("async")) &&
582 !element.has_attribute(&local_name!("defer"))
583 }
584
585 pub(crate) fn prepare(
587 &self,
588 cx: &mut JSContext,
589 introduction_type_override: Option<&'static CStr>,
590 ) {
591 let introduction_type =
592 introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT));
593
594 if self.already_started.get() {
596 return;
597 }
598
599 let was_parser_inserted = self.parser_inserted.get();
604 self.parser_inserted.set(false);
605
606 let element = self.upcast::<Element>();
609 let asynch = element.has_attribute(&local_name!("async"));
610 if was_parser_inserted && !asynch {
612 self.non_blocking.set(true);
613 }
614
615 if self.prepare_the_script_text(cx).is_err() {
618 return;
619 }
620 let text: Cow<'_, str> = Cow::Owned(String::from(self.script_text.borrow().str()));
622 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
624 return;
625 }
626
627 if !self.upcast::<Node>().is_connected() {
629 return;
630 }
631
632 let script_type = if let Some(ty) = self.get_script_type() {
633 ty
635 } else {
636 return;
638 };
639
640 if was_parser_inserted {
644 self.parser_inserted.set(true);
645 self.non_blocking.set(false);
646 }
647
648 self.already_started.set(true);
650
651 let doc = self.owner_document();
653 self.preparation_time_document.set(Some(&doc));
654
655 if self.parser_inserted.get() && *self.parser_document != *doc {
659 return;
660 }
661
662 if !doc.scripting_enabled() {
664 return;
665 }
666
667 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
669 return;
670 }
671
672 let global = &doc.global();
673
674 if !element.has_attribute(&local_name!("src")) &&
676 global
677 .get_csp_list()
678 .should_elements_inline_type_behavior_be_blocked(
679 cx,
680 global,
681 element,
682 InlineCheckType::Script,
683 &text,
684 self.line_number as u32,
685 )
686 {
687 warn!("Blocking inline script due to CSP");
688 return;
689 }
690
691 if script_type == ScriptType::Classic {
693 let for_attribute = element.get_attribute_string_value(&local_name!("for"));
694 let event_attribute = element.get_attribute_string_value(&local_name!("event"));
695 if let (Some(for_attribute), Some(event_attribute)) = (for_attribute, event_attribute) {
696 let for_value = for_attribute.trim_matches(HTML_SPACE_CHARACTERS);
697 if !for_value.eq_ignore_ascii_case("window") {
698 return;
699 }
700
701 let event_value = event_attribute.trim_matches(HTML_SPACE_CHARACTERS);
702 if !event_value.eq_ignore_ascii_case("onload") &&
703 !event_value.eq_ignore_ascii_case("onload()")
704 {
705 return;
706 }
707 }
708 }
709
710 let encoding = element
715 .get_attribute_string_value(&local_name!("charset"))
716 .and_then(|charset| Encoding::for_label(charset.as_bytes()))
717 .unwrap_or_else(|| doc.encoding());
718
719 let cors_setting = cors_setting_for_element(element);
721
722 let module_credentials_mode = cors_settings_attribute_credential_mode(element);
724
725 let cryptographic_nonce =
730 if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
731 element.nonce_value().trim().to_owned()
732 } else {
733 String::new()
734 };
735
736 let integrity_val = element.get_attribute_string_value(&local_name!("integrity"));
739 let integrity_val_is_none = integrity_val.is_none();
740 let integrity_metadata = integrity_val.unwrap_or_default();
741
742 let referrer_policy = referrer_policy_for_element(element);
744
745 let parser_metadata = if self.parser_inserted.get() {
750 ParserMetadata::ParserInserted
751 } else {
752 ParserMetadata::NotParserInserted
753 };
754
755 let mut script_fetch_options = ScriptFetchOptions {
757 cryptographic_nonce,
758 integrity_metadata,
759 parser_metadata,
760 referrer_policy,
761 credentials_mode: module_credentials_mode,
762 render_blocking: false,
763 };
764
765 let base_url = doc.base_url();
768
769 let kind = self.get_script_kind(script_type);
770 let delayed_document = self.get_script_active_document(kind);
771
772 if let Some(src) = element.get_attribute_string_value(&local_name!("src")) {
775 if script_type == ScriptType::ImportMap {
777 self.queue_error_event();
780 return;
781 }
782
783 if src.is_empty() {
785 self.queue_error_event();
786 return;
787 }
788
789 self.from_an_external_file.set(true);
791
792 let url = match base_url.join(&src) {
794 Ok(url) => url,
795 Err(_) => {
796 warn!("error parsing URL for script {}", src);
797 self.queue_error_event();
798 return;
799 },
800 };
801 let url = ensure_blob_referenced_by_url_is_kept_alive(global, url);
802
803 if self.potentially_render_blocking() && doc.allows_adding_render_blocking_elements() {
805 self.marked_as_render_blocking.set(true);
806 doc.increment_render_blocking_element_count();
807 }
808
809 self.delay_load_event(&delayed_document, url.url());
811
812 if self.marked_as_render_blocking.get() {
814 script_fetch_options.render_blocking = true;
815 }
816
817 match script_type {
819 ScriptType::Classic => {
820 fetch_a_classic_script(
822 self,
823 kind,
824 url,
825 cors_setting,
826 script_fetch_options,
827 encoding,
828 );
829 },
830 ScriptType::Module => {
831 if integrity_val_is_none {
834 script_fetch_options.integrity_metadata = global
835 .import_map()
836 .resolve_a_module_integrity_metadata(&url.url());
837 }
838
839 let script = DomRoot::from_ref(self);
840
841 fetch_an_external_module_script(
843 cx,
844 url,
845 global,
846 script_fetch_options,
847 move |cx, module_tree| {
848 let load = module_tree.map(Script::Module).ok_or(());
849 *script.result.borrow_mut() = Some(Box::new(load));
850
851 script.finish_fetching_a_script(cx, kind);
852 },
853 );
854 },
855 ScriptType::ImportMap => (),
856 }
857 } else {
858 assert!(!text.is_empty());
861
862 match script_type {
864 ScriptType::Classic => {
865 let script = self.global().create_a_classic_script(
868 cx,
869 text,
870 base_url,
871 ScriptOptions::empty(),
872 script_fetch_options,
873 introduction_type,
874 self.line_number as u32,
875 );
876 let result = Ok(Script::Classic(script));
877
878 if was_parser_inserted &&
879 doc.get_current_parser()
880 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
881 doc.has_a_stylesheet_that_is_blocking_scripts()
882 {
883 doc.set_pending_parsing_blocking_script(self, Some(result));
885 } else {
886 self.execute(cx, result);
888 }
889 return;
890 },
891 ScriptType::Module => {
892 self.delay_load_event(&delayed_document, base_url.clone());
894
895 if self.potentially_render_blocking() &&
897 doc.allows_adding_render_blocking_elements()
898 {
899 self.marked_as_render_blocking.set(true);
901 doc.increment_render_blocking_element_count();
902
903 script_fetch_options.render_blocking = true;
905 }
906
907 let script = DomRoot::from_ref(self);
908 fetch_inline_module_script(
911 cx,
912 global,
913 text,
914 base_url,
915 script_fetch_options,
916 self.line_number as u32,
917 introduction_type,
918 move |_, module_tree| {
919 let load = module_tree.map(Script::Module).ok_or(());
920 *script.result.borrow_mut() = Some(Box::new(load));
921
922 let trusted = Trusted::new(&*script);
923
924 script
926 .owner_global()
927 .task_manager()
928 .networking_task_source()
929 .queue(task!(terminate_module_fetch: move |cx| {
930 let element = trusted.root();
932 element.finish_fetching_a_script(cx, kind);
933 }));
934 },
935 );
936 },
937 ScriptType::ImportMap => {
938 let import_map_result = parse_an_import_map_string(cx, global, &text, base_url);
941 let script = Script::ImportMap(import_map_result);
942
943 self.execute(cx, Ok(script));
945 return;
946 },
947 }
948 }
949
950 match kind {
952 ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
953 ExternalScriptKind::ParsingBlocking => {
954 delayed_document.set_pending_parsing_blocking_script(self, None);
955 },
956 ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
957 ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
958 }
959 }
960
961 pub(crate) fn execute(&self, cx: &mut JSContext, result: ScriptResult) {
963 let doc = self.owner_document();
965
966 if *doc != *self.preparation_time_document.get().unwrap() {
968 return;
969 }
970
971 if self.marked_as_render_blocking.replace(false) {
973 self.marked_as_render_blocking.set(false);
974 doc.decrement_render_blocking_element_count();
975 }
976
977 let script = match result {
978 Err(_) => {
980 self.upcast::<EventTarget>().fire_event(cx, atom!("error"));
981 return;
982 },
983
984 Ok(script) => script,
985 };
986
987 let neutralized_doc =
991 if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
992 let doc = self.owner_document();
993 doc.incr_ignore_destructive_writes_counter();
994 Some(doc)
995 } else {
996 None
997 };
998
999 let document = self.owner_document();
1000
1001 match script {
1002 Script::Classic(script) => {
1003 let old_script = document.GetCurrentScript();
1005
1006 if self.upcast::<Node>().is_in_a_shadow_tree() {
1009 document.set_current_script(None)
1010 } else {
1011 document.set_current_script(Some(self))
1012 }
1013
1014 _ = self.owner_global().run_a_classic_script(
1016 cx,
1017 script,
1018 RethrowErrors::No,
1019 None, );
1021
1022 document.set_current_script(old_script.as_deref());
1024 },
1025 Script::Module(module_tree) => {
1026 document.set_current_script(None);
1028
1029 self.owner_global()
1031 .run_a_module_script(cx, module_tree, false);
1032 },
1033 Script::ImportMap(import_map) => {
1034 register_import_map(cx, &self.owner_global(), import_map);
1036 },
1037 }
1038
1039 if let Some(doc) = neutralized_doc {
1042 doc.decr_ignore_destructive_writes_counter();
1043 }
1044
1045 if self.from_an_external_file.get() {
1047 self.upcast::<EventTarget>().fire_event(cx, atom!("load"));
1048 }
1049 }
1050
1051 pub(crate) fn queue_error_event(&self) {
1052 self.owner_global()
1053 .task_manager()
1054 .dom_manipulation_task_source()
1055 .queue_simple_event(self.upcast(), atom!("error"));
1056 }
1057
1058 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1060 let element = self.upcast::<Element>();
1061
1062 let type_attr = element.get_attribute_string_value(&local_name!("type"));
1063 let language_attr = element.get_attribute_string_value(&local_name!("language"));
1064
1065 match (type_attr, language_attr) {
1066 (Some(ty), _) if ty.is_empty() => {
1067 debug!("script type empty, inferring js");
1068 Some(ScriptType::Classic)
1069 },
1070 (None, Some(lang)) if lang.is_empty() => {
1071 debug!("script type empty, inferring js");
1072 Some(ScriptType::Classic)
1073 },
1074 (None, None) => {
1075 debug!("script type empty, inferring js");
1076 Some(ScriptType::Classic)
1077 },
1078 (None, Some(lang)) => {
1079 debug!("script language={}", lang);
1080 let language = format!("text/{}", lang);
1081
1082 if SCRIPT_JS_MIMES
1083 .iter()
1084 .any(|mime| language.eq_ignore_ascii_case(mime))
1085 {
1086 Some(ScriptType::Classic)
1087 } else {
1088 None
1089 }
1090 },
1091 (Some(ty), _) => {
1092 debug!("script type={}", ty);
1093
1094 if ty
1095 .trim_matches(HTML_SPACE_CHARACTERS)
1096 .eq_ignore_ascii_case("module")
1097 {
1098 return Some(ScriptType::Module);
1099 }
1100
1101 if ty
1102 .trim_matches(HTML_SPACE_CHARACTERS)
1103 .eq_ignore_ascii_case("importmap")
1104 {
1105 return Some(ScriptType::ImportMap);
1106 }
1107
1108 if SCRIPT_JS_MIMES.iter().any(|mime| {
1109 ty.trim_matches(HTML_SPACE_CHARACTERS)
1110 .eq_ignore_ascii_case(mime)
1111 }) {
1112 Some(ScriptType::Classic)
1113 } else {
1114 None
1115 }
1116 },
1117 }
1118 }
1119
1120 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1121 self.parser_inserted.set(parser_inserted);
1122 }
1123
1124 pub(crate) fn set_already_started(&self, already_started: bool) {
1125 self.already_started.set(already_started);
1126 }
1127
1128 fn text(&self) -> DOMString {
1129 match self.Text() {
1130 TrustedScriptOrString::String(value) => value,
1131 TrustedScriptOrString::TrustedScript(trusted_script) => {
1132 DOMString::from(trusted_script.to_string())
1133 },
1134 }
1135 }
1136}
1137
1138impl VirtualMethods for HTMLScriptElement {
1139 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1140 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1141 }
1142
1143 fn attribute_mutated(
1144 &self,
1145 cx: &mut JSContext,
1146 attr: AttrRef<'_>,
1147 mutation: AttributeMutation,
1148 ) {
1149 self.super_type()
1150 .unwrap()
1151 .attribute_mutated(cx, attr, mutation);
1152 if *attr.local_name() == local_name!("src") {
1153 if let AttributeMutation::Set(_) = mutation &&
1154 !self.parser_inserted.get() &&
1155 self.upcast::<Node>().is_connected()
1156 {
1157 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1158 }
1159 } else if *attr.local_name() == local_name!("blocking") &&
1160 !self.has_render_blocking_attribute() &&
1161 self.marked_as_render_blocking.replace(false)
1162 {
1163 let document = self.owner_document();
1164 document.decrement_render_blocking_element_count();
1165 }
1166 }
1167
1168 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
1170 if let Some(s) = self.super_type() {
1171 s.children_changed(cx, mutation);
1172 }
1173
1174 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1175 let script = DomRoot::from_ref(self);
1176 self.owner_document().add_delayed_task(
1180 task!(ScriptPrepare: |cx, script: DomRoot<HTMLScriptElement>| {
1181 script.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1182 }),
1183 );
1184 }
1185 }
1186
1187 fn post_connection_steps(&self, cx: &mut JSContext) {
1189 if let Some(s) = self.super_type() {
1190 s.post_connection_steps(cx);
1191 }
1192
1193 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1194 self.prepare(cx, Some(IntroductionType::INJECTED_SCRIPT));
1195 }
1196 }
1197
1198 fn cloning_steps(
1199 &self,
1200 cx: &mut JSContext,
1201 copy: &Node,
1202 maybe_doc: Option<&Document>,
1203 clone_children: CloneChildrenFlag,
1204 ) {
1205 if let Some(s) = self.super_type() {
1206 s.cloning_steps(cx, copy, maybe_doc, clone_children);
1207 }
1208
1209 if self.already_started.get() {
1211 copy.downcast::<HTMLScriptElement>()
1212 .unwrap()
1213 .set_already_started(true);
1214 }
1215 }
1216
1217 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
1218 self.super_type().unwrap().unbind_from_tree(cx, context);
1219
1220 if self.marked_as_render_blocking.replace(false) {
1221 let document = self.owner_document();
1222 document.decrement_render_blocking_element_count();
1223 }
1224 }
1225}
1226
1227impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1228 fn Src(&self) -> TrustedScriptURLOrUSVString {
1230 let element = self.upcast::<Element>();
1231 element.get_trusted_type_url_attribute(&local_name!("src"))
1232 }
1233
1234 fn SetSrc(&self, cx: &mut JSContext, value: TrustedScriptURLOrUSVString) -> Fallible<()> {
1236 let element = self.upcast::<Element>();
1237 let local_name = &local_name!("src");
1238 let value = TrustedScriptURL::get_trusted_type_compliant_string(
1239 cx,
1240 &element.owner_global(),
1241 value,
1242 &format!("HTMLScriptElement {}", local_name),
1243 )?;
1244 element.set_attribute(cx, local_name, AttrValue::String(value.str().to_owned()));
1245 Ok(())
1246 }
1247
1248 make_getter!(Type, "type");
1250 make_setter!(SetType, "type");
1252
1253 make_getter!(Charset, "charset");
1255 make_setter!(SetCharset, "charset");
1257
1258 fn Async(&self) -> bool {
1260 self.non_blocking.get() ||
1261 self.upcast::<Element>()
1262 .has_attribute(&local_name!("async"))
1263 }
1264
1265 fn SetAsync(&self, cx: &mut JSContext, value: bool) {
1267 self.non_blocking.set(false);
1268 self.upcast::<Element>()
1269 .set_bool_attribute(cx, &local_name!("async"), value);
1270 }
1271
1272 make_bool_getter!(Defer, "defer");
1274 make_bool_setter!(SetDefer, "defer");
1276
1277 fn Blocking(&self, cx: &mut JSContext) -> DomRoot<DOMTokenList> {
1279 self.blocking.or_init(|| {
1280 DOMTokenList::new(
1281 cx,
1282 self.upcast(),
1283 &local_name!("blocking"),
1284 Some(vec![Atom::from("render")]),
1285 )
1286 })
1287 }
1288
1289 make_bool_getter!(NoModule, "nomodule");
1291 make_bool_setter!(SetNoModule, "nomodule");
1293
1294 make_getter!(Integrity, "integrity");
1296 make_setter!(SetIntegrity, "integrity");
1298
1299 make_getter!(Event, "event");
1301 make_setter!(SetEvent, "event");
1303
1304 make_getter!(HtmlFor, "for");
1306 make_setter!(SetHtmlFor, "for");
1308
1309 fn GetCrossOrigin(&self) -> Option<DOMString> {
1311 reflect_cross_origin_attribute(self.upcast::<Element>())
1312 }
1313
1314 fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1316 set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1317 }
1318
1319 fn ReferrerPolicy(&self) -> DOMString {
1321 reflect_referrer_policy_attribute(self.upcast::<Element>())
1322 }
1323
1324 make_setter!(SetReferrerPolicy, "referrerpolicy");
1326
1327 fn InnerText(&self) -> TrustedScriptOrString {
1329 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1331 }
1332
1333 fn SetInnerText(&self, cx: &mut JSContext, input: TrustedScriptOrString) -> Fallible<()> {
1335 let value = TrustedScript::get_trusted_type_compliant_string(
1338 cx,
1339 &self.owner_global(),
1340 input,
1341 "HTMLScriptElement innerText",
1342 )?;
1343 *self.script_text.borrow_mut() = value.clone();
1344 self.upcast::<HTMLElement>().set_inner_text(cx, value);
1346 Ok(())
1347 }
1348
1349 fn Text(&self) -> TrustedScriptOrString {
1351 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1352 }
1353
1354 fn SetText(&self, cx: &mut JSContext, value: TrustedScriptOrString) -> Fallible<()> {
1356 let value = TrustedScript::get_trusted_type_compliant_string(
1359 cx,
1360 &self.owner_global(),
1361 value,
1362 "HTMLScriptElement text",
1363 )?;
1364 *self.script_text.borrow_mut() = value.clone();
1366 Node::string_replace_all(cx, value, self.upcast::<Node>());
1368 Ok(())
1369 }
1370
1371 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1373 Some(TrustedScriptOrString::String(
1375 self.upcast::<Node>().GetTextContent()?,
1376 ))
1377 }
1378
1379 fn SetTextContent(
1381 &self,
1382 cx: &mut JSContext,
1383 value: Option<TrustedScriptOrString>,
1384 ) -> Fallible<()> {
1385 let value = TrustedScript::get_trusted_type_compliant_string(
1388 cx,
1389 &self.owner_global(),
1390 value.unwrap_or(TrustedScriptOrString::String(DOMString::new())),
1391 "HTMLScriptElement textContent",
1392 )?;
1393 *self.script_text.borrow_mut() = value.clone();
1395 self.upcast::<Node>()
1397 .set_text_content_for_element(cx, Some(value));
1398 Ok(())
1399 }
1400
1401 fn Supports(_window: &Window, type_: DOMString) -> bool {
1403 matches!(&*type_.str(), "classic" | "module" | "importmap")
1406 }
1407}
1408
1409pub fn substitute_with_local_script(
1410 script_source: &str,
1411 script: &mut Cow<'_, str>,
1412 url: &ServoUrl,
1413) {
1414 let mut path = PathBuf::from(script_source);
1415 path = path.join(&url[url::Position::BeforeHost..url::Position::AfterPath]);
1416 debug!("Attempting to read script stored at: {:?}", path);
1417 match read_to_string(path.clone()) {
1418 Ok(local_script) => {
1419 debug!("Found script stored at: {:?}", path);
1420 *script = Cow::Owned(local_script);
1421 },
1422 Err(why) => warn!("Could not restore script from file {:?}", why),
1423 }
1424}
1425
1426#[derive(Clone, Copy)]
1427enum ExternalScriptKind {
1428 Deferred,
1429 ParsingBlocking,
1430 AsapInOrder,
1431 Asap,
1432}