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