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::performance::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 pub code: SourceCode,
236 #[no_trace]
237 pub url: ServoUrl,
238 external: bool,
239 pub 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 let line_number = if script.external {
1064 1
1065 } else {
1066 self.line_number as u32
1067 };
1068 self.owner_window().as_global_scope().run_a_classic_script(
1069 &script,
1070 line_number,
1071 Some(introduction_type),
1072 can_gc,
1073 );
1074 document.set_current_script(old_script.as_deref());
1075 },
1076 ScriptType::Module => {
1077 document.set_current_script(None);
1078 self.run_a_module_script(&script, false, can_gc);
1079 },
1080 ScriptType::ImportMap => {
1081 register_import_map(&self.owner_global(), script.import_map, can_gc);
1083 },
1084 }
1085
1086 if let Some(doc) = neutralized_doc {
1089 doc.decr_ignore_destructive_writes_counter();
1090 }
1091
1092 if script.external {
1094 self.dispatch_load_event(can_gc);
1095 }
1096 }
1097
1098 #[allow(unsafe_code)]
1099 pub(crate) fn run_a_module_script(
1101 &self,
1102 script: &ScriptOrigin,
1103 _rethrow_errors: bool,
1104 can_gc: CanGc,
1105 ) {
1106 let document = self.owner_document();
1109 if !document.is_fully_active() || !document.scripting_enabled() {
1110 return;
1111 }
1112
1113 let window = self.owner_window();
1115 let global = window.as_global_scope();
1116 let _aes = AutoEntryScript::new(global);
1117
1118 let tree = if script.external {
1119 global.get_module_map().borrow().get(&script.url).cloned()
1120 } else {
1121 global
1122 .get_inline_module_map()
1123 .borrow()
1124 .get(&self.id.clone())
1125 .cloned()
1126 };
1127
1128 if let Some(module_tree) = tree {
1129 {
1131 let module_error = module_tree.get_rethrow_error().borrow();
1132 let network_error = module_tree.get_network_error().borrow();
1133 if module_error.is_some() && network_error.is_none() {
1134 module_tree.report_error(global, can_gc);
1135 return;
1136 }
1137 }
1138
1139 let record = module_tree
1140 .get_record()
1141 .borrow()
1142 .as_ref()
1143 .map(|record| record.handle());
1144
1145 if let Some(record) = record {
1146 rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue());
1147 let evaluated =
1148 module_tree.execute_module(global, record, rval.handle_mut().into(), can_gc);
1149
1150 if let Err(exception) = evaluated {
1151 module_tree.set_rethrow_error(exception);
1152 module_tree.report_error(global, can_gc);
1153 }
1154 }
1155 }
1156 }
1157
1158 pub(crate) fn queue_error_event(&self) {
1159 self.owner_global()
1160 .task_manager()
1161 .dom_manipulation_task_source()
1162 .queue_simple_event(self.upcast(), atom!("error"));
1163 }
1164
1165 pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) {
1166 self.dispatch_event(
1167 atom!("load"),
1168 EventBubbles::DoesNotBubble,
1169 EventCancelable::NotCancelable,
1170 can_gc,
1171 );
1172 }
1173
1174 pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) {
1175 self.dispatch_event(
1176 atom!("error"),
1177 EventBubbles::DoesNotBubble,
1178 EventCancelable::NotCancelable,
1179 can_gc,
1180 );
1181 }
1182
1183 pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1185 let element = self.upcast::<Element>();
1186
1187 let type_attr = element.get_attribute(&ns!(), &local_name!("type"));
1188 let language_attr = element.get_attribute(&ns!(), &local_name!("language"));
1189
1190 match (
1191 type_attr.as_ref().map(|t| t.value()),
1192 language_attr.as_ref().map(|l| l.value()),
1193 ) {
1194 (Some(ref ty), _) if ty.is_empty() => {
1195 debug!("script type empty, inferring js");
1196 Some(ScriptType::Classic)
1197 },
1198 (None, Some(ref lang)) if lang.is_empty() => {
1199 debug!("script type empty, inferring js");
1200 Some(ScriptType::Classic)
1201 },
1202 (None, None) => {
1203 debug!("script type empty, inferring js");
1204 Some(ScriptType::Classic)
1205 },
1206 (None, Some(ref lang)) => {
1207 debug!("script language={}", &***lang);
1208 let language = format!("text/{}", &***lang);
1209
1210 if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1211 Some(ScriptType::Classic)
1212 } else {
1213 None
1214 }
1215 },
1216 (Some(ref ty), _) => {
1217 debug!("script type={}", &***ty);
1218
1219 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1220 return Some(ScriptType::Module);
1221 }
1222
1223 if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1224 return Some(ScriptType::ImportMap);
1225 }
1226
1227 if SCRIPT_JS_MIMES
1228 .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1229 {
1230 Some(ScriptType::Classic)
1231 } else {
1232 None
1233 }
1234 },
1235 }
1236 }
1237
1238 pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1239 self.parser_inserted.set(parser_inserted);
1240 }
1241
1242 pub(crate) fn get_parser_inserted(&self) -> bool {
1243 self.parser_inserted.get()
1244 }
1245
1246 pub(crate) fn set_already_started(&self, already_started: bool) {
1247 self.already_started.set(already_started);
1248 }
1249
1250 pub(crate) fn get_non_blocking(&self) -> bool {
1251 self.non_blocking.get()
1252 }
1253
1254 fn dispatch_event(
1255 &self,
1256 type_: Atom,
1257 bubbles: EventBubbles,
1258 cancelable: EventCancelable,
1259 can_gc: CanGc,
1260 ) -> bool {
1261 let window = self.owner_window();
1262 let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc);
1263 event.fire(self.upcast(), can_gc)
1264 }
1265
1266 fn text(&self) -> DOMString {
1267 match self.Text() {
1268 TrustedScriptOrString::String(value) => value,
1269 TrustedScriptOrString::TrustedScript(trusted_script) => {
1270 DOMString::from(trusted_script.to_string())
1271 },
1272 }
1273 }
1274}
1275
1276impl VirtualMethods for HTMLScriptElement {
1277 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1278 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1279 }
1280
1281 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1282 self.super_type()
1283 .unwrap()
1284 .attribute_mutated(attr, mutation, can_gc);
1285 if *attr.local_name() == local_name!("src") {
1286 if let AttributeMutation::Set(_) = mutation {
1287 if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1288 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1289 }
1290 }
1291 }
1292 }
1293
1294 fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
1296 if let Some(s) = self.super_type() {
1297 s.children_changed(mutation, can_gc);
1298 }
1299
1300 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1301 let script = DomRoot::from_ref(self);
1302 self.owner_document().add_delayed_task(
1306 task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| {
1307 script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1308 }),
1309 );
1310 }
1311 }
1312
1313 fn post_connection_steps(&self, can_gc: CanGc) {
1315 if let Some(s) = self.super_type() {
1316 s.post_connection_steps(can_gc);
1317 }
1318
1319 if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1320 self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1321 }
1322 }
1323
1324 fn cloning_steps(
1325 &self,
1326 copy: &Node,
1327 maybe_doc: Option<&Document>,
1328 clone_children: CloneChildrenFlag,
1329 can_gc: CanGc,
1330 ) {
1331 if let Some(s) = self.super_type() {
1332 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
1333 }
1334
1335 if self.already_started.get() {
1337 copy.downcast::<HTMLScriptElement>()
1338 .unwrap()
1339 .set_already_started(true);
1340 }
1341 }
1342}
1343
1344impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1345 fn Src(&self) -> TrustedScriptURLOrUSVString {
1347 let element = self.upcast::<Element>();
1348 element.get_trusted_type_url_attribute(&local_name!("src"))
1349 }
1350
1351 fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> {
1353 let element = self.upcast::<Element>();
1354 let local_name = &local_name!("src");
1355 let value = TrustedScriptURL::get_trusted_script_url_compliant_string(
1356 &element.owner_global(),
1357 value,
1358 "HTMLScriptElement",
1359 local_name,
1360 can_gc,
1361 )?;
1362 element.set_attribute(
1363 local_name,
1364 AttrValue::String(value.str().to_owned()),
1365 can_gc,
1366 );
1367 Ok(())
1368 }
1369
1370 make_getter!(Type, "type");
1372 make_setter!(SetType, "type");
1374
1375 make_getter!(Charset, "charset");
1377 make_setter!(SetCharset, "charset");
1379
1380 fn Async(&self) -> bool {
1382 self.non_blocking.get() ||
1383 self.upcast::<Element>()
1384 .has_attribute(&local_name!("async"))
1385 }
1386
1387 fn SetAsync(&self, value: bool, can_gc: CanGc) {
1389 self.non_blocking.set(false);
1390 self.upcast::<Element>()
1391 .set_bool_attribute(&local_name!("async"), value, can_gc);
1392 }
1393
1394 make_bool_getter!(Defer, "defer");
1396 make_bool_setter!(SetDefer, "defer");
1398
1399 make_bool_getter!(NoModule, "nomodule");
1401 make_bool_setter!(SetNoModule, "nomodule");
1403
1404 make_getter!(Integrity, "integrity");
1406 make_setter!(SetIntegrity, "integrity");
1408
1409 make_getter!(Event, "event");
1411 make_setter!(SetEvent, "event");
1413
1414 make_getter!(HtmlFor, "for");
1416 make_setter!(SetHtmlFor, "for");
1418
1419 fn GetCrossOrigin(&self) -> Option<DOMString> {
1421 reflect_cross_origin_attribute(self.upcast::<Element>())
1422 }
1423
1424 fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1426 set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1427 }
1428
1429 fn ReferrerPolicy(&self) -> DOMString {
1431 reflect_referrer_policy_attribute(self.upcast::<Element>())
1432 }
1433
1434 make_setter!(SetReferrerPolicy, "referrerpolicy");
1436
1437 fn InnerText(&self) -> TrustedScriptOrString {
1439 TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1441 }
1442
1443 fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1445 let value = TrustedScript::get_trusted_script_compliant_string(
1448 &self.owner_global(),
1449 input,
1450 "HTMLScriptElement innerText",
1451 can_gc,
1452 )?;
1453 *self.script_text.borrow_mut() = value.clone();
1454 self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1456 Ok(())
1457 }
1458
1459 fn Text(&self) -> TrustedScriptOrString {
1461 TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1462 }
1463
1464 fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1466 let value = TrustedScript::get_trusted_script_compliant_string(
1469 &self.owner_global(),
1470 value,
1471 "HTMLScriptElement text",
1472 can_gc,
1473 )?;
1474 *self.script_text.borrow_mut() = value.clone();
1476 Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1478 Ok(())
1479 }
1480
1481 fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1483 Some(TrustedScriptOrString::String(
1485 self.upcast::<Node>().GetTextContent()?,
1486 ))
1487 }
1488
1489 fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1491 let value = TrustedScript::get_trusted_script_compliant_string(
1494 &self.owner_global(),
1495 value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1496 "HTMLScriptElement textContent",
1497 can_gc,
1498 )?;
1499 *self.script_text.borrow_mut() = value.clone();
1501 self.upcast::<Node>()
1503 .set_text_content_for_element(Some(value), can_gc);
1504 Ok(())
1505 }
1506
1507 fn Supports(_window: &Window, type_: DOMString) -> bool {
1509 matches!(&*type_.str(), "classic" | "module" | "importmap")
1512 }
1513}
1514
1515#[derive(Clone, Copy)]
1516enum ExternalScriptKind {
1517 Deferred,
1518 ParsingBlocking,
1519 AsapInOrder,
1520 Asap,
1521}