1use std::cell::Cell;
5use std::ffi::CStr;
6use std::fs::read_to_string;
7use std::path::PathBuf;
8use std::rc::Rc;
9
10use base::id::{PipelineId, WebViewId};
11use dom_struct::dom_struct;
12use encoding_rs::Encoding;
13use html5ever::{LocalName, Prefix, local_name, ns};
14use js::jsval::UndefinedValue;
15use js::rust::{HandleObject, Stencil};
16use net_traits::http_status::HttpStatus;
17use net_traits::policy_container::PolicyContainer;
18use net_traits::request::{
19 CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata,
20 RequestBuilder, RequestId,
21};
22use net_traits::{
23 FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming,
24 ResourceTimingType,
25};
26use script_bindings::domstring::BytesView;
27use servo_url::{ImmutableOrigin, ServoUrl};
28use style::attr::AttrValue;
29use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
30use stylo_atoms::Atom;
31use uuid::Uuid;
32
33use crate::document_loader::LoadType;
34use crate::dom::attr::Attr;
35use crate::dom::bindings::cell::DomRefCell;
36use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
38use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
39use crate::dom::bindings::codegen::UnionTypes::{
40 TrustedScriptOrString, TrustedScriptURLOrUSVString,
41};
42use crate::dom::bindings::error::{Error, Fallible};
43use crate::dom::bindings::inheritance::Castable;
44use crate::dom::bindings::refcounted::Trusted;
45use crate::dom::bindings::reflector::DomGlobal;
46use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
47use crate::dom::bindings::settings_stack::AutoEntryScript;
48use crate::dom::bindings::str::DOMString;
49use crate::dom::bindings::trace::NoTrace;
50use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
51use crate::dom::document::Document;
52use crate::dom::element::{
53 AttributeMutation, Element, ElementCreator, cors_setting_for_element,
54 referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
55 set_cross_origin_attribute,
56};
57use crate::dom::event::{Event, EventBubbles, EventCancelable};
58use crate::dom::globalscope::GlobalScope;
59use crate::dom::html::htmlelement::HTMLElement;
60use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits};
61use crate::dom::performanceresourcetiming::InitiatorType;
62use crate::dom::trustedscript::TrustedScript;
63use crate::dom::trustedscripturl::TrustedScriptURL;
64use crate::dom::virtualmethods::VirtualMethods;
65use crate::dom::window::Window;
66use crate::fetch::create_a_potential_cors_request;
67use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
68use crate::realms::enter_realm;
69use crate::script_module::{
70 ImportMap, ModuleOwner, ScriptFetchOptions, fetch_external_module_script,
71 fetch_inline_module_script, parse_an_import_map_string, register_import_map,
72};
73use crate::script_runtime::{CanGc, IntroductionType};
74use crate::unminify::{ScriptSource, unminify_js};
75
76impl ScriptSource for ScriptOrigin {
77 fn unminified_dir(&self) -> Option<String> {
78 self.unminified_dir.clone()
79 }
80
81 fn extract_bytes(&self) -> BytesView<'_> {
82 match &self.code {
83 SourceCode::Text(text) => text.as_bytes(),
84 SourceCode::Compiled(compiled_source_code) => {
85 compiled_source_code.original_text.as_bytes()
86 },
87 }
88 }
89
90 fn rewrite_source(&mut self, source: Rc<DOMString>) {
91 self.code = SourceCode::Text(source);
92 }
93
94 fn url(&self) -> ServoUrl {
95 self.url.clone()
96 }
97
98 fn is_external(&self) -> bool {
99 self.external
100 }
101}
102
103#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
105pub(crate) struct ScriptId(#[no_trace] Uuid);
106
107#[dom_struct]
108pub(crate) struct HTMLScriptElement {
109 htmlelement: HTMLElement,
110
111 already_started: Cell<bool>,
113
114 parser_inserted: Cell<bool>,
116
117 non_blocking: Cell<bool>,
121
122 parser_document: Dom<Document>,
125
126 preparation_time_document: MutNullableDom<Document>,
129
130 line_number: u64,
132
133 #[ignore_malloc_size_of = "Defined in uuid"]
135 id: ScriptId,
136
137 script_text: DomRefCell<DOMString>,
139
140 #[no_trace]
143 introduction_type_override: Cell<Option<&'static CStr>>,
144}
145
146impl HTMLScriptElement {
147 fn new_inherited(
148 local_name: LocalName,
149 prefix: Option<Prefix>,
150 document: &Document,
151 creator: ElementCreator,
152 ) -> HTMLScriptElement {
153 HTMLScriptElement {
154 id: ScriptId(Uuid::new_v4()),
155 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
156 already_started: Cell::new(false),
157 parser_inserted: Cell::new(creator.is_parser_created()),
158 non_blocking: Cell::new(!creator.is_parser_created()),
159 parser_document: Dom::from_ref(document),
160 preparation_time_document: MutNullableDom::new(None),
161 line_number: creator.return_line_number(),
162 script_text: DomRefCell::new(DOMString::new()),
163 introduction_type_override: Cell::new(None),
164 }
165 }
166
167 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
168 pub(crate) fn new(
169 local_name: LocalName,
170 prefix: Option<Prefix>,
171 document: &Document,
172 proto: Option<HandleObject>,
173 creator: ElementCreator,
174 can_gc: CanGc,
175 ) -> DomRoot<HTMLScriptElement> {
176 Node::reflect_node_with_proto(
177 Box::new(HTMLScriptElement::new_inherited(
178 local_name, prefix, document, creator,
179 )),
180 document,
181 proto,
182 can_gc,
183 )
184 }
185
186 pub(crate) fn get_script_id(&self) -> ScriptId {
187 self.id
188 }
189}
190
191pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
194 "application/ecmascript",
195 "application/javascript",
196 "application/x-ecmascript",
197 "application/x-javascript",
198 "text/ecmascript",
199 "text/javascript",
200 "text/javascript1.0",
201 "text/javascript1.1",
202 "text/javascript1.2",
203 "text/javascript1.3",
204 "text/javascript1.4",
205 "text/javascript1.5",
206 "text/jscript",
207 "text/livescript",
208 "text/x-ecmascript",
209 "text/x-javascript",
210];
211
212#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
213pub(crate) enum ScriptType {
214 Classic,
215 Module,
216 ImportMap,
217}
218
219#[derive(JSTraceable, MallocSizeOf)]
220pub(crate) struct CompiledSourceCode {
221 #[ignore_malloc_size_of = "SM handles JS values"]
222 pub(crate) source_code: Stencil,
223 #[conditional_malloc_size_of = "Rc is hard"]
224 pub(crate) original_text: Rc<DOMString>,
225}
226
227#[derive(JSTraceable, MallocSizeOf)]
228pub(crate) enum SourceCode {
229 Text(#[conditional_malloc_size_of] Rc<DOMString>),
230 Compiled(CompiledSourceCode),
231}
232
233#[derive(JSTraceable, MallocSizeOf)]
234pub(crate) struct ScriptOrigin {
235 code: SourceCode,
236 #[no_trace]
237 url: ServoUrl,
238 external: bool,
239 fetch_options: ScriptFetchOptions,
240 type_: ScriptType,
241 unminified_dir: Option<String>,
242 import_map: Fallible<ImportMap>,
243}
244
245impl ScriptOrigin {
246 pub(crate) fn internal(
247 text: Rc<DOMString>,
248 url: ServoUrl,
249 fetch_options: ScriptFetchOptions,
250 type_: ScriptType,
251 unminified_dir: Option<String>,
252 import_map: Fallible<ImportMap>,
253 ) -> ScriptOrigin {
254 ScriptOrigin {
255 code: SourceCode::Text(text),
256 url,
257 external: false,
258 fetch_options,
259 type_,
260 unminified_dir,
261 import_map,
262 }
263 }
264
265 pub(crate) fn external(
266 text: Rc<DOMString>,
267 url: ServoUrl,
268 fetch_options: ScriptFetchOptions,
269 type_: ScriptType,
270 unminified_dir: Option<String>,
271 ) -> ScriptOrigin {
272 ScriptOrigin {
273 code: SourceCode::Text(text),
274 url,
275 external: true,
276 fetch_options,
277 type_,
278 unminified_dir,
279 import_map: Err(Error::NotFound(None)),
280 }
281 }
282
283 pub(crate) fn text(&self) -> Rc<DOMString> {
284 match &self.code {
285 SourceCode::Text(text) => Rc::clone(text),
286 SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text),
287 }
288 }
289}
290
291fn finish_fetching_a_classic_script(
293 elem: &HTMLScriptElement,
294 script_kind: ExternalScriptKind,
295 url: ServoUrl,
296 load: ScriptResult,
297 can_gc: CanGc,
298) {
299 let document;
302
303 match script_kind {
304 ExternalScriptKind::Asap => {
305 document = elem.preparation_time_document.get().unwrap();
306 document.asap_script_loaded(elem, load, can_gc)
307 },
308 ExternalScriptKind::AsapInOrder => {
309 document = elem.preparation_time_document.get().unwrap();
310 document.asap_in_order_script_loaded(elem, load, can_gc)
311 },
312 ExternalScriptKind::Deferred => {
313 document = elem.parser_document.as_rooted();
314 document.deferred_script_loaded(elem, load, can_gc);
315 },
316 ExternalScriptKind::ParsingBlocking => {
317 document = elem.parser_document.as_rooted();
318 document.pending_parsing_blocking_script_loaded(elem, load, can_gc);
319 },
320 }
321
322 document.finish_load(LoadType::Script(url), can_gc);
323}
324
325pub(crate) type ScriptResult = Result<ScriptOrigin, NoTrace<NetworkError>>;
326
327struct ClassicContext {
329 elem: Trusted<HTMLScriptElement>,
331 kind: ExternalScriptKind,
333 character_encoding: &'static Encoding,
336 data: Vec<u8>,
338 metadata: Option<Metadata>,
340 url: ServoUrl,
342 status: Result<(), NetworkError>,
344 fetch_options: ScriptFetchOptions,
346 resource_timing: ResourceFetchTiming,
348}
349
350impl FetchResponseListener for ClassicContext {
351 fn process_request_body(&mut self, _: RequestId) {}
353
354 fn process_request_eof(&mut self, _: RequestId) {}
356
357 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
358 self.metadata = metadata.ok().map(|meta| match meta {
359 FetchMetadata::Unfiltered(m) => m,
360 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
361 });
362
363 let status = self
364 .metadata
365 .as_ref()
366 .map(|m| m.status.clone())
367 .unwrap_or_else(HttpStatus::new_error);
368
369 self.status = {
370 if status.is_error() {
371 Err(NetworkError::Internal(
372 "No http status code received".to_owned(),
373 ))
374 } else if status.is_success() {
375 Ok(())
376 } else {
377 Err(NetworkError::Internal(format!(
378 "HTTP error code {}",
379 status.code()
380 )))
381 }
382 };
383 }
384
385 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
386 if self.status.is_ok() {
387 self.data.append(&mut chunk);
388 }
389 }
390
391 #[allow(unsafe_code)]
394 fn process_response_eof(
395 &mut self,
396 _: RequestId,
397 response: Result<ResourceFetchTiming, NetworkError>,
398 ) {
399 let (source_text, final_url) = match (response.as_ref(), self.status.as_ref()) {
400 (Err(err), _) | (_, Err(err)) => {
401 finish_fetching_a_classic_script(
403 &self.elem.root(),
404 self.kind,
405 self.url.clone(),
406 Err(NoTrace(err.clone())),
407 CanGc::note(),
408 );
409 return;
410 },
411 (Ok(_), Ok(_)) => {
412 let metadata = self.metadata.take().unwrap();
413
414 let encoding = metadata
416 .charset
417 .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
418 .unwrap_or(self.character_encoding);
419
420 let (source_text, _, _) = encoding.decode(&self.data);
422 (source_text, metadata.final_url)
423 },
424 };
425
426 let elem = self.elem.root();
427 let global = elem.global();
428 let _ar = enter_realm(&*global);
430
431 let load = ScriptOrigin::external(
462 Rc::new(DOMString::from(source_text)),
463 final_url.clone(),
464 self.fetch_options.clone(),
465 ScriptType::Classic,
466 elem.parser_document.global().unminified_js_dir(),
467 );
468 finish_fetching_a_classic_script(
469 &elem,
470 self.kind,
471 self.url.clone(),
472 Ok(load),
473 CanGc::note(),
474 );
475 }
477
478 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
479 &mut self.resource_timing
480 }
481
482 fn resource_timing(&self) -> &ResourceFetchTiming {
483 &self.resource_timing
484 }
485
486 fn submit_resource_timing(&mut self) {
487 network_listener::submit_timing(self, CanGc::note())
488 }
489
490 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
491 let global = &self.resource_timing_global();
492 let elem = self.elem.root();
493 let source_position = elem
494 .upcast::<Element>()
495 .compute_source_position(elem.line_number as u32);
496 global.report_csp_violations(violations, Some(elem.upcast()), Some(source_position));
497 }
498}
499
500impl ResourceTimingListener for ClassicContext {
501 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
502 let initiator_type = InitiatorType::LocalName(
503 self.elem
504 .root()
505 .upcast::<Element>()
506 .local_name()
507 .to_string(),
508 );
509 (initiator_type, self.url.clone())
510 }
511
512 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
513 self.elem.root().owner_document().global()
514 }
515}
516
517impl PreInvoke for ClassicContext {}
518
519#[allow(clippy::too_many_arguments)]
522pub(crate) fn script_fetch_request(
523 webview_id: WebViewId,
524 url: ServoUrl,
525 cors_setting: Option<CorsSettings>,
526 origin: ImmutableOrigin,
527 pipeline_id: PipelineId,
528 options: ScriptFetchOptions,
529 insecure_requests_policy: InsecureRequestsPolicy,
530 has_trustworthy_ancestor_origin: bool,
531 policy_container: PolicyContainer,
532) -> RequestBuilder {
533 create_a_potential_cors_request(
536 Some(webview_id),
537 url,
538 Destination::Script,
539 cors_setting,
540 None,
541 options.referrer,
542 insecure_requests_policy,
543 has_trustworthy_ancestor_origin,
544 policy_container,
545 )
546 .origin(origin)
547 .pipeline_id(Some(pipeline_id))
548 .parser_metadata(options.parser_metadata)
549 .integrity_metadata(options.integrity_metadata.clone())
550 .referrer_policy(options.referrer_policy)
551 .cryptographic_nonce_metadata(options.cryptographic_nonce)
552}
553
554fn fetch_a_classic_script(
556 script: &HTMLScriptElement,
557 kind: ExternalScriptKind,
558 url: ServoUrl,
559 cors_setting: Option<CorsSettings>,
560 options: ScriptFetchOptions,
561 character_encoding: &'static Encoding,
562) {
563 let doc = script.owner_document();
565 let global = script.global();
566 let request = script_fetch_request(
567 doc.webview_id(),
568 url.clone(),
569 cors_setting,
570 doc.origin().immutable().clone(),
571 global.pipeline_id(),
572 options.clone(),
573 doc.insecure_requests_policy(),
574 doc.has_trustworthy_ancestor_origin(),
575 global.policy_container(),
576 );
577 let request = doc.prepare_request(request);
578
579 let context = ClassicContext {
582 elem: Trusted::new(script),
583 kind,
584 character_encoding,
585 data: vec![],
586 metadata: None,
587 url: url.clone(),
588 status: Ok(()),
589 fetch_options: options,
590 resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
591 };
592 doc.fetch(LoadType::Script(url), request, context);
593}
594
595impl HTMLScriptElement {
596 pub(crate) fn set_initial_script_text(&self) {
598 *self.script_text.borrow_mut() = self.text();
599 }
600
601 fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> {
603 if self.script_text.borrow().clone() != self.text() {
607 *self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string(
608 &self.owner_global(),
609 self.Text(),
610 "HTMLScriptElement text",
611 can_gc,
612 )?;
613 }
614
615 Ok(())
616 }
617
618 pub(crate) fn prepare(&self, introduction_type_override: Option<&'static CStr>, can_gc: CanGc) {
620 self.introduction_type_override
621 .set(introduction_type_override);
622
623 if self.already_started.get() {
625 return;
626 }
627
628 let was_parser_inserted = self.parser_inserted.get();
633 self.parser_inserted.set(false);
634
635 let element = self.upcast::<Element>();
638 let asynch = element.has_attribute(&local_name!("async"));
639 if was_parser_inserted && !asynch {
641 self.non_blocking.set(true);
642 }
643
644 if self.prepare_the_script_text(can_gc).is_err() {
647 return;
648 }
649 let text = self.script_text.borrow().clone();
651 if text.is_empty() && !element.has_attribute(&local_name!("src")) {
653 return;
654 }
655
656 if !self.upcast::<Node>().is_connected() {
658 return;
659 }
660
661 let script_type = if let Some(ty) = self.get_script_type() {
662 ty
664 } else {
665 return;
667 };
668
669 if was_parser_inserted {
673 self.parser_inserted.set(true);
674 self.non_blocking.set(false);
675 }
676
677 self.already_started.set(true);
679
680 let doc = self.owner_document();
682 self.preparation_time_document.set(Some(&doc));
683
684 if self.parser_inserted.get() && *self.parser_document != *doc {
688 return;
689 }
690
691 if !doc.scripting_enabled() {
693 return;
694 }
695
696 if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
698 return;
699 }
700
701 let global = &doc.global();
702
703 if !element.has_attribute(&local_name!("src")) &&
705 global
706 .get_csp_list()
707 .should_elements_inline_type_behavior_be_blocked(
708 global,
709 element,
710 InlineCheckType::Script,
711 &text.str(),
712 )
713 {
714 warn!("Blocking inline script due to CSP");
715 return;
716 }
717
718 if script_type == ScriptType::Classic {
720 let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
721 let event_attribute = element.get_attribute(&ns!(), &local_name!("event"));
722 if let (Some(ref for_attribute), Some(ref event_attribute)) =
723 (for_attribute, event_attribute)
724 {
725 let for_value = for_attribute.value().to_ascii_lowercase();
726 let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
727 if for_value != "window" {
728 return;
729 }
730
731 let event_value = event_attribute.value().to_ascii_lowercase();
732 let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
733 if event_value != "onload" && event_value != "onload()" {
734 return;
735 }
736 }
737 }
738
739 let encoding = element
744 .get_attribute(&ns!(), &local_name!("charset"))
745 .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
746 .unwrap_or_else(|| doc.encoding());
747
748 let cors_setting = cors_setting_for_element(element);
750
751 let module_credentials_mode = match script_type {
753 ScriptType::Classic => CredentialsMode::CredentialsSameOrigin,
754 ScriptType::Module | ScriptType::ImportMap => reflect_cross_origin_attribute(element)
755 .map_or(
756 CredentialsMode::CredentialsSameOrigin,
757 |attr| match &*attr.str() {
758 "use-credentials" => CredentialsMode::Include,
759 "anonymous" => CredentialsMode::CredentialsSameOrigin,
760 _ => CredentialsMode::CredentialsSameOrigin,
761 },
762 ),
763 };
764
765 let cryptographic_nonce = self.upcast::<Element>().nonce_value();
767
768 let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
771 let integrity_val = im_attribute.as_ref().map(|a| a.value());
772 let integrity_metadata = match integrity_val {
773 Some(ref value) => &***value,
774 None => "",
775 };
776
777 let referrer_policy = referrer_policy_for_element(self.upcast::<Element>());
779
780 let parser_metadata = if self.parser_inserted.get() {
785 ParserMetadata::ParserInserted
786 } else {
787 ParserMetadata::NotParserInserted
788 };
789
790 let options = ScriptFetchOptions {
792 cryptographic_nonce,
793 integrity_metadata: integrity_metadata.to_owned(),
794 parser_metadata,
795 referrer: self.global().get_referrer(),
796 referrer_policy,
797 credentials_mode: module_credentials_mode,
798 };
799
800 let base_url = doc.base_url();
805 if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
806 if script_type == ScriptType::ImportMap {
810 self.queue_error_event();
813 return;
814 }
815
816 let src = src.value();
818
819 if src.is_empty() {
821 self.queue_error_event();
822 return;
823 }
824
825 let url = match base_url.join(&src) {
830 Ok(url) => url,
831 Err(_) => {
832 warn!("error parsing URL for script {}", &**src);
833 self.queue_error_event();
834 return;
835 },
836 };
837
838 match script_type {
845 ScriptType::Classic => {
846 let kind = if element.has_attribute(&local_name!("defer")) &&
847 was_parser_inserted &&
848 !asynch
849 {
850 ExternalScriptKind::Deferred
852 } else if was_parser_inserted && !asynch {
853 ExternalScriptKind::ParsingBlocking
855 } else if !asynch && !self.non_blocking.get() {
856 ExternalScriptKind::AsapInOrder
858 } else {
859 ExternalScriptKind::Asap
861 };
862
863 fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
865
866 match kind {
868 ExternalScriptKind::Deferred => doc.add_deferred_script(self),
869 ExternalScriptKind::ParsingBlocking => {
870 doc.set_pending_parsing_blocking_script(self, None)
871 },
872 ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
873 ExternalScriptKind::Asap => doc.add_asap_script(self),
874 }
875 },
876 ScriptType::Module => {
877 fetch_external_module_script(
879 ModuleOwner::Window(Trusted::new(self)),
880 url.clone(),
881 Destination::Script,
882 options,
883 can_gc,
884 );
885
886 if !asynch && was_parser_inserted {
887 doc.add_deferred_script(self);
889 } else if !asynch && !self.non_blocking.get() {
890 doc.push_asap_in_order_script(self);
892 } else {
893 doc.add_asap_script(self);
895 };
896 },
897 ScriptType::ImportMap => (),
898 }
899 } else {
900 assert!(!text.is_empty());
903
904 let text_rc = Rc::new(text);
905
906 match script_type {
908 ScriptType::Classic => {
909 let result = Ok(ScriptOrigin::internal(
910 text_rc,
911 base_url,
912 options,
913 script_type,
914 self.global().unminified_js_dir(),
915 Err(Error::NotFound(None)),
916 ));
917
918 if was_parser_inserted &&
919 doc.get_current_parser()
920 .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
921 doc.get_script_blocking_stylesheets_count() > 0
922 {
923 doc.set_pending_parsing_blocking_script(self, Some(result));
925 } else {
926 self.execute(result, can_gc);
928 }
929 },
930 ScriptType::Module => {
931 if !asynch && was_parser_inserted {
935 doc.add_deferred_script(self);
936 } else if !asynch && !self.non_blocking.get() {
937 doc.push_asap_in_order_script(self);
938 } else {
939 doc.add_asap_script(self);
940 };
941
942 fetch_inline_module_script(
943 ModuleOwner::Window(Trusted::new(self)),
944 text_rc,
945 base_url.clone(),
946 self.id,
947 options,
948 self.line_number,
949 can_gc,
950 );
951 },
952 ScriptType::ImportMap => {
953 let import_map_result = parse_an_import_map_string(
956 ModuleOwner::Window(Trusted::new(self)),
957 Rc::clone(&text_rc),
958 base_url.clone(),
959 can_gc,
960 );
961 let result = Ok(ScriptOrigin::internal(
962 text_rc,
963 base_url,
964 options,
965 script_type,
966 self.global().unminified_js_dir(),
967 import_map_result,
968 ));
969
970 self.execute(result, can_gc);
972 },
973 }
974 }
975 }
976
977 fn substitute_with_local_script(&self, script: &mut ScriptOrigin) {
978 if self
979 .parser_document
980 .window()
981 .local_script_source()
982 .is_none() ||
983 !script.external
984 {
985 return;
986 }
987 let mut path = PathBuf::from(
988 self.parser_document
989 .window()
990 .local_script_source()
991 .clone()
992 .unwrap(),
993 );
994 path = path.join(&script.url[url::Position::BeforeHost..]);
995 debug!("Attempting to read script stored at: {:?}", path);
996 match read_to_string(path.clone()) {
997 Ok(local_script) => {
998 debug!("Found script stored at: {:?}", path);
999 script.code = SourceCode::Text(Rc::new(DOMString::from(local_script)));
1000 },
1001 Err(why) => warn!("Could not restore script from file {:?}", why),
1002 }
1003 }
1004
1005 pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) {
1007 let doc = self.owner_document();
1009
1010 if *doc != *self.preparation_time_document.get().unwrap() {
1012 return;
1013 }
1014
1015 let mut script = match result {
1017 Err(e) => {
1019 warn!("error loading script {:?}", e);
1020 self.dispatch_error_event(can_gc);
1021 return;
1022 },
1023
1024 Ok(script) => script,
1025 };
1026
1027 if script.type_ == ScriptType::Classic {
1028 unminify_js(&mut script);
1029 self.substitute_with_local_script(&mut script);
1030 }
1031
1032 let neutralized_doc = if script.external || script.type_ == ScriptType::Module {
1036 debug!("loading external script, url = {}", script.url);
1037 let doc = self.owner_document();
1038 doc.incr_ignore_destructive_writes_counter();
1039 Some(doc)
1040 } else {
1041 None
1042 };
1043
1044 let document = self.owner_document();
1046 let old_script = document.GetCurrentScript();
1047 let introduction_type =
1048 self.introduction_type_override
1049 .get()
1050 .unwrap_or(if script.external {
1051 IntroductionType::SRC_SCRIPT
1052 } else {
1053 IntroductionType::INLINE_SCRIPT
1054 });
1055
1056 match script.type_ {
1057 ScriptType::Classic => {
1058 if self.upcast::<Node>().is_in_a_shadow_tree() {
1059 document.set_current_script(None)
1060 } else {
1061 document.set_current_script(Some(self))
1062 }
1063 self.run_a_classic_script(&script, can_gc, Some(introduction_type));
1064 document.set_current_script(old_script.as_deref());
1065 },
1066 ScriptType::Module => {
1067 document.set_current_script(None);
1068 self.run_a_module_script(&script, false, can_gc);
1069 },
1070 ScriptType::ImportMap => {
1071 register_import_map(&self.owner_global(), script.import_map, can_gc);
1073 },
1074 }
1075
1076 if let Some(doc) = neutralized_doc {
1079 doc.decr_ignore_destructive_writes_counter();
1080 }
1081
1082 if script.external {
1084 self.dispatch_load_event(can_gc);
1085 }
1086 }
1087
1088 pub(crate) fn run_a_classic_script(
1090 &self,
1091 script: &ScriptOrigin,
1092 can_gc: CanGc,
1093 introduction_type: Option<&'static CStr>,
1094 ) {
1095 let document = self.owner_document();
1098 if !document.is_fully_active() || !document.scripting_enabled() {
1099 return;
1100 }
1101
1102 let window = self.owner_window();
1104 let line_number = if script.external {
1105 1
1106 } else {
1107 self.line_number as u32
1108 };
1109 rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue());
1110 _ = window
1111 .as_global_scope()
1112 .evaluate_script_on_global_with_result(
1113 &script.code,
1114 script.url.as_str(),
1115 rval.handle_mut(),
1116 line_number,
1117 script.fetch_options.clone(),
1118 script.url.clone(),
1119 can_gc,
1120 introduction_type,
1121 );
1122 }
1123
1124 #[allow(unsafe_code)]
1125 pub(crate) fn run_a_module_script(
1127 &self,
1128 script: &ScriptOrigin,
1129 _rethrow_errors: bool,
1130 can_gc: CanGc,
1131 ) {
1132 let document = self.owner_document();
1135 if !document.is_fully_active() || !document.scripting_enabled() {
1136 return;
1137 }
1138
1139 let window = self.owner_window();
1141 let global = window.as_global_scope();
1142 let _aes = AutoEntryScript::new(global);
1143
1144 let tree = if script.external {
1145 global.get_module_map().borrow().get(&script.url).cloned()
1146 } else {
1147 global
1148 .get_inline_module_map()
1149 .borrow()
1150 .get(&self.id.clone())
1151 .cloned()
1152 };
1153
1154 if let Some(module_tree) = tree {
1155 {
1157 let module_error = module_tree.get_rethrow_error().borrow();
1158 let network_error = module_tree.get_network_error().borrow();
1159 if module_error.is_some() && network_error.is_none() {
1160 module_tree.report_error(global, can_gc);
1161 return;
1162 }
1163 }
1164
1165 let record = module_tree
1166 .get_record()
1167 .borrow()
1168 .as_ref()
1169 .map(|record| record.handle());
1170
1171 if let Some(record) = record {
1172 rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue());
1173 let evaluated =
1174 module_tree.execute_module(global, record, rval.handle_mut().into(), can_gc);
1175
1176 if let Err(exception) = evaluated {
1177 module_tree.set_rethrow_error(exception);
1178 module_tree.report_error(global, can_gc);
1179 }
1180 }
1181 }
1182 }
1183
1184 pub(crate) fn queue_error_event(&self) {
1185 self.owner_global()
1186 .task_manager()
1187 .dom_manipulation_task_source()
1188 .queue_simple_event(self.upcast(), atom!("error"));
1189 }
1190
1191 pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) {
1192 self.dispatch_event(
1193 atom!("load"),
1194 EventBubbles::DoesNotBubble,
1195 EventCancelable::NotCancelable,
1196 can_gc,
1197 );
1198 }
1199
1200 pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) {
1201 self.dispatch_event(
1202 atom!("error"),
1203 EventBubbles::DoesNotBubble,
1204 EventCancelable::NotCancelable,
1205 can_gc,
1206 );
1207 }
1208
1209 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1211 let element = self.upcast::<Element>();
1212
1213 let type_attr = element.get_attribute(&ns!(), &local_name!("type"));
1214 let language_attr = element.get_attribute(&ns!(), &local_name!("language"));
1215
1216 match (
1217 type_attr.as_ref().map(|t| t.value()),
1218 language_attr.as_ref().map(|l| l.value()),
1219 ) {
1220 (Some(ref ty), _) if ty.is_empty() => {
1221 debug!("script type empty, inferring js");
1222 Some(ScriptType::Classic)
1223 },
1224 (None, Some(ref lang)) if lang.is_empty() => {
1225 debug!("script type empty, inferring js");
1226 Some(ScriptType::Classic)
1227 },
1228 (None, None) => {
1229 debug!("script type empty, inferring js");
1230 Some(ScriptType::Classic)
1231 },
1232 (None, Some(ref lang)) => {
1233 debug!("script language={}", &***lang);
1234 let language = format!("text/{}", &***lang);
1235
1236 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1237 Some(ScriptType::Classic)
1238 } else {
1239 None
1240 }
1241 },
1242 (Some(ref ty), _) => {
1243 debug!("script type={}", &***ty);
1244
1245 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1246 return Some(ScriptType::Module);
1247 }
1248
1249 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1250 return Some(ScriptType::ImportMap);
1251 }
1252
1253 if SCRIPT_JS_MIMES
1254 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1255 {
1256 Some(ScriptType::Classic)
1257 } else {
1258 None
1259 }
1260 },
1261 }
1262 }
1263
1264 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1265 self.parser_inserted.set(parser_inserted);
1266 }
1267
1268 pub(crate) fn get_parser_inserted(&self) -> bool {
1269 self.parser_inserted.get()
1270 }
1271
1272 pub(crate) fn set_already_started(&self, already_started: bool) {
1273 self.already_started.set(already_started);
1274 }
1275
1276 pub(crate) fn get_non_blocking(&self) -> bool {
1277 self.non_blocking.get()
1278 }
1279
1280 fn dispatch_event(
1281 &self,
1282 type_: Atom,
1283 bubbles: EventBubbles,
1284 cancelable: EventCancelable,
1285 can_gc: CanGc,
1286 ) -> bool {
1287 let window = self.owner_window();
1288 let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc);
1289 event.fire(self.upcast(), can_gc)
1290 }
1291
1292 fn text(&self) -> DOMString {
1293 match self.Text() {
1294 TrustedScriptOrString::String(value) => value,
1295 TrustedScriptOrString::TrustedScript(trusted_script) => {
1296 DOMString::from(trusted_script.to_string())
1297 },
1298 }
1299 }
1300}
1301
1302impl VirtualMethods for HTMLScriptElement {
1303 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1304 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1305 }
1306
1307 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1308 self.super_type()
1309 .unwrap()
1310 .attribute_mutated(attr, mutation, can_gc);
1311 if *attr.local_name() == local_name!("src") {
1312 if let AttributeMutation::Set(_) = mutation {
1313 if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1314 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1315 }
1316 }
1317 }
1318 }
1319
1320 fn children_changed(&self, mutation: &ChildrenMutation) {
1322 if let Some(s) = self.super_type() {
1323 s.children_changed(mutation);
1324 }
1325
1326 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1327 let script = DomRoot::from_ref(self);
1328 self.owner_document().add_delayed_task(
1332 task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| {
1333 script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1334 }),
1335 );
1336 }
1337 }
1338
1339 fn post_connection_steps(&self) {
1341 if let Some(s) = self.super_type() {
1342 s.post_connection_steps();
1343 }
1344
1345 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1346 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1347 }
1348 }
1349
1350 fn cloning_steps(
1351 &self,
1352 copy: &Node,
1353 maybe_doc: Option<&Document>,
1354 clone_children: CloneChildrenFlag,
1355 can_gc: CanGc,
1356 ) {
1357 if let Some(s) = self.super_type() {
1358 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
1359 }
1360
1361 if self.already_started.get() {
1363 copy.downcast::<HTMLScriptElement>()
1364 .unwrap()
1365 .set_already_started(true);
1366 }
1367 }
1368}
1369
1370impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1371 fn Src(&self) -> TrustedScriptURLOrUSVString {
1373 let element = self.upcast::<Element>();
1374 element.get_trusted_type_url_attribute(&local_name!("src"))
1375 }
1376
1377 fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> {
1379 let element = self.upcast::<Element>();
1380 let local_name = &local_name!("src");
1381 let value = TrustedScriptURL::get_trusted_script_url_compliant_string(
1382 &element.owner_global(),
1383 value,
1384 "HTMLScriptElement",
1385 local_name,
1386 can_gc,
1387 )?;
1388 element.set_attribute(
1389 local_name,
1390 AttrValue::String(value.str().to_owned()),
1391 can_gc,
1392 );
1393 Ok(())
1394 }
1395
1396 make_getter!(Type, "type");
1398 make_setter!(SetType, "type");
1400
1401 make_getter!(Charset, "charset");
1403 make_setter!(SetCharset, "charset");
1405
1406 fn Async(&self) -> bool {
1408 self.non_blocking.get() ||
1409 self.upcast::<Element>()
1410 .has_attribute(&local_name!("async"))
1411 }
1412
1413 fn SetAsync(&self, value: bool, can_gc: CanGc) {
1415 self.non_blocking.set(false);
1416 self.upcast::<Element>()
1417 .set_bool_attribute(&local_name!("async"), value, can_gc);
1418 }
1419
1420 make_bool_getter!(Defer, "defer");
1422 make_bool_setter!(SetDefer, "defer");
1424
1425 make_bool_getter!(NoModule, "nomodule");
1427 make_bool_setter!(SetNoModule, "nomodule");
1429
1430 make_getter!(Integrity, "integrity");
1432 make_setter!(SetIntegrity, "integrity");
1434
1435 make_getter!(Event, "event");
1437 make_setter!(SetEvent, "event");
1439
1440 make_getter!(HtmlFor, "for");
1442 make_setter!(SetHtmlFor, "for");
1444
1445 fn GetCrossOrigin(&self) -> Option<DOMString> {
1447 reflect_cross_origin_attribute(self.upcast::<Element>())
1448 }
1449
1450 fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1452 set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1453 }
1454
1455 fn ReferrerPolicy(&self) -> DOMString {
1457 reflect_referrer_policy_attribute(self.upcast::<Element>())
1458 }
1459
1460 make_setter!(SetReferrerPolicy, "referrerpolicy");
1462
1463 fn InnerText(&self) -> TrustedScriptOrString {
1465 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1467 }
1468
1469 fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1471 let value = TrustedScript::get_trusted_script_compliant_string(
1474 &self.owner_global(),
1475 input,
1476 "HTMLScriptElement innerText",
1477 can_gc,
1478 )?;
1479 *self.script_text.borrow_mut() = value.clone();
1480 self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1482 Ok(())
1483 }
1484
1485 fn Text(&self) -> TrustedScriptOrString {
1487 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1488 }
1489
1490 fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1492 let value = TrustedScript::get_trusted_script_compliant_string(
1495 &self.owner_global(),
1496 value,
1497 "HTMLScriptElement text",
1498 can_gc,
1499 )?;
1500 *self.script_text.borrow_mut() = value.clone();
1502 Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1504 Ok(())
1505 }
1506
1507 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1509 Some(TrustedScriptOrString::String(
1511 self.upcast::<Node>().GetTextContent()?,
1512 ))
1513 }
1514
1515 fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1517 let value = TrustedScript::get_trusted_script_compliant_string(
1520 &self.owner_global(),
1521 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1522 "HTMLScriptElement textContent",
1523 can_gc,
1524 )?;
1525 *self.script_text.borrow_mut() = value.clone();
1527 self.upcast::<Node>()
1529 .set_text_content_for_element(Some(value), can_gc);
1530 Ok(())
1531 }
1532
1533 fn Supports(_window: &Window, type_: DOMString) -> bool {
1535 matches!(&*type_.str(), "classic" | "module" | "importmap")
1538 }
1539}
1540
1541#[derive(Clone, Copy)]
1542enum ExternalScriptKind {
1543 Deferred,
1544 ParsingBlocking,
1545 AsapInOrder,
1546 Asap,
1547}