script/dom/html/
htmlscriptelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4use 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/// An unique id for script element.
104#[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    /// <https://html.spec.whatwg.org/multipage/#already-started>
112    already_started: Cell<bool>,
113
114    /// <https://html.spec.whatwg.org/multipage/#parser-inserted>
115    parser_inserted: Cell<bool>,
116
117    /// <https://html.spec.whatwg.org/multipage/#non-blocking>
118    ///
119    /// (currently unused)
120    non_blocking: Cell<bool>,
121
122    /// Document of the parser that created this element
123    /// <https://html.spec.whatwg.org/multipage/#parser-document>
124    parser_document: Dom<Document>,
125
126    /// Prevents scripts that move between documents during preparation from executing.
127    /// <https://html.spec.whatwg.org/multipage/#preparation-time-document>
128    preparation_time_document: MutNullableDom<Document>,
129
130    /// Track line line_number
131    line_number: u64,
132
133    /// Unique id for each script element
134    #[ignore_malloc_size_of = "Defined in uuid"]
135    id: ScriptId,
136
137    /// <https://w3c.github.io/trusted-types/dist/spec/#htmlscriptelement-script-text>
138    script_text: DomRefCell<DOMString>,
139
140    /// `introductionType` value to set in the `CompileOptionsWrapper`, overriding the usual
141    /// `srcScript` or `inlineScript` that this script would normally use.
142    #[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
191/// Supported script types as defined by
192/// <https://html.spec.whatwg.org/multipage/#javascript-mime-type>.
193pub(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
291/// Final steps of <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
292fn finish_fetching_a_classic_script(
293    elem: &HTMLScriptElement,
294    script_kind: ExternalScriptKind,
295    url: ServoUrl,
296    load: ScriptResult,
297    can_gc: CanGc,
298) {
299    // Step 33. The "steps to run when the result is ready" for each type of script in 33.2-33.5.
300    // of https://html.spec.whatwg.org/multipage/#prepare-the-script-element
301    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
327/// The context required for asynchronously loading an external script source.
328struct ClassicContext {
329    /// The element that initiated the request.
330    elem: Trusted<HTMLScriptElement>,
331    /// The kind of external script.
332    kind: ExternalScriptKind,
333    /// The (fallback) character encoding argument to the "fetch a classic
334    /// script" algorithm.
335    character_encoding: &'static Encoding,
336    /// The response body received to date.
337    data: Vec<u8>,
338    /// The response metadata received to date.
339    metadata: Option<Metadata>,
340    /// The initial URL requested.
341    url: ServoUrl,
342    /// Indicates whether the request failed, and why
343    status: Result<(), NetworkError>,
344    /// The fetch options of the script
345    fetch_options: ScriptFetchOptions,
346    /// Timing object for this resource
347    resource_timing: ResourceFetchTiming,
348}
349
350impl FetchResponseListener for ClassicContext {
351    // TODO(KiChjang): Perhaps add custom steps to perform fetch here?
352    fn process_request_body(&mut self, _: RequestId) {}
353
354    // TODO(KiChjang): Perhaps add custom steps to perform fetch here?
355    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    /// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
392    /// step 4-9
393    #[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                // Step 6, response is an error.
402                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                // Step 7.
415                let encoding = metadata
416                    .charset
417                    .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
418                    .unwrap_or(self.character_encoding);
419
420                // Step 8.
421                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 cx = GlobalScope::get_cx();
429        let _ar = enter_realm(&*global);
430
431        /*
432        let options = unsafe { CompileOptionsWrapper::new(*cx, final_url.as_str(), 1) };
433
434        let can_compile_off_thread = pref!(dom_script_asynch) &&
435            unsafe { CanCompileOffThread(*cx, options.ptr as *const _, source_text.len()) };
436
437        if can_compile_off_thread {
438            let source_string = source_text.to_string();
439
440            let context = Box::new(OffThreadCompilationContext {
441                script_element: self.elem.clone(),
442                script_kind: self.kind,
443                final_url,
444                url: self.url.clone(),
445                task_source: elem.owner_global().task_manager().dom_manipulation_task_source(),
446                script_text: source_string,
447                fetch_options: self.fetch_options.clone(),
448            });
449
450            unsafe {
451                assert!(!CompileToStencilOffThread1(
452                    *cx,
453                    options.ptr as *const _,
454                    &mut transform_str_to_source_text(&context.script_text) as *mut _,
455                    Some(off_thread_compilation_callback),
456                    Box::into_raw(context) as *mut c_void,
457                )
458                .is_null());
459            }
460        } else {*/
461        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        // }
476    }
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/// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
520// This function is also used to prefetch a script in `script::dom::servoparser::prefetch`.
521#[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    // We intentionally ignore options' credentials_mode member for classic scripts.
534    // The mode is initialized by create_a_potential_cors_request.
535    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
554/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
555fn 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    // Step 1, 2.
564    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    // TODO: Step 3, Add custom steps to perform fetch
580
581    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    /// <https://w3c.github.io/trusted-types/dist/spec/#setting-slot-values-from-parser>
597    pub(crate) fn set_initial_script_text(&self) {
598        *self.script_text.borrow_mut() = self.text();
599    }
600
601    /// <https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-prepare-the-script-text>
602    fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> {
603        // Step 1. If script’s script text value is not equal to its child text content,
604        // set script’s script text to the result of executing
605        // Get Trusted Type compliant string, with the following arguments:
606        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    /// <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
619    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        // Step 1. If el's already started is true, then return.
624        if self.already_started.get() {
625            return;
626        }
627
628        // Step 2. Let parser document be el's parser document.
629        // TODO
630
631        // Step 3. Set el's parser document to null.
632        let was_parser_inserted = self.parser_inserted.get();
633        self.parser_inserted.set(false);
634
635        // Step 4.
636        // If parser document is non-null and el does not have an async attribute, then set el's force async to true.
637        let element = self.upcast::<Element>();
638        let asynch = element.has_attribute(&local_name!("async"));
639        // Note: confusingly, this is done if the element does *not* have an "async" attribute.
640        if was_parser_inserted && !asynch {
641            self.non_blocking.set(true);
642        }
643
644        // Step 5. Execute the Prepare the script text algorithm on el.
645        // If that algorithm threw an error, then return.
646        if self.prepare_the_script_text(can_gc).is_err() {
647            return;
648        }
649        // Step 5a. Let source text be el’s script text value.
650        let text = self.script_text.borrow().clone();
651        // Step 6. If el has no src attribute, and source text is the empty string, then return.
652        if text.is_empty() && !element.has_attribute(&local_name!("src")) {
653            return;
654        }
655
656        // Step 7. If el is not connected, then return.
657        if !self.upcast::<Node>().is_connected() {
658            return;
659        }
660
661        let script_type = if let Some(ty) = self.get_script_type() {
662            // Step 9-11.
663            ty
664        } else {
665            // Step 12. Otherwise, return. (No script is executed, and el's type is left as null.)
666            return;
667        };
668
669        // Step 13.
670        // If parser document is non-null, then set el's parser document back to parser document and set el's force
671        // async to false.
672        if was_parser_inserted {
673            self.parser_inserted.set(true);
674            self.non_blocking.set(false);
675        }
676
677        // Step 14. Set el's already started to true.
678        self.already_started.set(true);
679
680        // Step 15. Set el's preparation-time document to its node document.
681        let doc = self.owner_document();
682        self.preparation_time_document.set(Some(&doc));
683
684        // Step 16.
685        // If parser document is non-null, and parser document is not equal to el's preparation-time document, then
686        // return.
687        if self.parser_inserted.get() && *self.parser_document != *doc {
688            return;
689        }
690
691        // Step 17. If scripting is disabled for el, then return.
692        if !doc.scripting_enabled() {
693            return;
694        }
695
696        // Step 18. If el has a nomodule content attribute and its type is "classic", then return.
697        if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
698            return;
699        }
700
701        let global = &doc.global();
702
703        // Step 19. CSP.
704        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        // Step 20. If el has an event attribute and a for attribute, and el's type is "classic", then:
719        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        // Step 21. If el has a charset attribute, then let encoding be the result of getting
740        // an encoding from the value of the charset attribute.
741        // If el does not have a charset attribute, or if getting an encoding failed,
742        // then let encoding be el's node document's the encoding.
743        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        // Step 22. CORS setting.
749        let cors_setting = cors_setting_for_element(element);
750
751        // Step 23. Module script credentials mode.
752        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        // Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
766        let cryptographic_nonce = self.upcast::<Element>().nonce_value();
767
768        // Step 25. If el has an integrity attribute, then let integrity metadata be that attribute's value.
769        // Otherwise, let integrity metadata be the empty string.
770        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        // Step 26. Let referrer policy be the current state of el's referrerpolicy content attribute.
778        let referrer_policy = referrer_policy_for_element(self.upcast::<Element>());
779
780        // TODO: Step 27. Fetch priority.
781
782        // Step 28. Let parser metadata be "parser-inserted" if el is parser-inserted,
783        // and "not-parser-inserted" otherwise.
784        let parser_metadata = if self.parser_inserted.get() {
785            ParserMetadata::ParserInserted
786        } else {
787            ParserMetadata::NotParserInserted
788        };
789
790        // Step 29. Fetch options.
791        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        // Step 30. Let settings object be el's node document's relevant settings object.
801        // This is done by passing ModuleOwner in step 31.11 and step 32.2.
802        // What we actually need is global's import map eventually.
803
804        let base_url = doc.base_url();
805        if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
806            // Step 31. If el has a src content attribute, then:
807
808            // Step 31.1. If el's type is "importmap".
809            if script_type == ScriptType::ImportMap {
810                // then queue an element task on the DOM manipulation task source
811                // given el to fire an event named error at el, and return.
812                self.queue_error_event();
813                return;
814            }
815
816            // Step 31.2. Let src be the value of el's src attribute.
817            let src = src.value();
818
819            // Step 31.3. If src is the empty string.
820            if src.is_empty() {
821                self.queue_error_event();
822                return;
823            }
824
825            // Step 31.4. Set el's from an external file to true.
826            // The "from an external file"" flag is stored in ScriptOrigin.
827
828            // Step 31.5-31.6. Parse URL.
829            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            // TODO:
839            // Step 31.7. If el is potentially render-blocking, then block rendering on el.
840            // Step 31.8. Set el's delaying the load event to true.
841            // Step 31.9. If el is currently render-blocking, then set options's render-blocking to true.
842
843            // Step 31.11. Switch on el's type:
844            match script_type {
845                ScriptType::Classic => {
846                    let kind = if element.has_attribute(&local_name!("defer")) &&
847                        was_parser_inserted &&
848                        !asynch
849                    {
850                        // Step 33.4: classic, has src, has defer, was parser-inserted, is not async.
851                        ExternalScriptKind::Deferred
852                    } else if was_parser_inserted && !asynch {
853                        // Step 33.5: classic, has src, was parser-inserted, is not async.
854                        ExternalScriptKind::ParsingBlocking
855                    } else if !asynch && !self.non_blocking.get() {
856                        // Step 33.3: classic, has src, is not async, is not non-blocking.
857                        ExternalScriptKind::AsapInOrder
858                    } else {
859                        // Step 33.2: classic, has src.
860                        ExternalScriptKind::Asap
861                    };
862
863                    // Step 31.11. Fetch a classic script.
864                    fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
865
866                    // Step 33.2/33.3/33.4/33.5, substeps 1-2. Add el to the corresponding script list.
867                    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                    // Step 31.11. Fetch an external module script graph.
878                    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                        // 33.4: module, not async, parser-inserted
888                        doc.add_deferred_script(self);
889                    } else if !asynch && !self.non_blocking.get() {
890                        // 33.3: module, not parser-inserted
891                        doc.push_asap_in_order_script(self);
892                    } else {
893                        // 33.2: module, async
894                        doc.add_asap_script(self);
895                    };
896                },
897                ScriptType::ImportMap => (),
898            }
899        } else {
900            // Step 32. If el does not have a src content attribute:
901
902            assert!(!text.is_empty());
903
904            let text_rc = Rc::new(text);
905
906            // Step 32.2: Switch on el's type:
907            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                        // Step 34.2: classic, has no src, was parser-inserted, is blocked on stylesheet.
924                        doc.set_pending_parsing_blocking_script(self, Some(result));
925                    } else {
926                        // Step 34.3: otherwise.
927                        self.execute(result, can_gc);
928                    }
929                },
930                ScriptType::Module => {
931                    // We should add inline module script elements
932                    // into those vectors in case that there's no
933                    // descendants in the inline module script.
934                    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                    // Step 32.1 Let result be the result of creating an import map
954                    // parse result given source text and base URL.
955                    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                    // Step 34.3
971                    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    /// <https://html.spec.whatwg.org/multipage/#execute-the-script-element>
1006    pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) {
1007        // Step 1. Let document be el's node document.
1008        let doc = self.owner_document();
1009
1010        // Step 2. If el's preparation-time document is not equal to document, then return.
1011        if *doc != *self.preparation_time_document.get().unwrap() {
1012            return;
1013        }
1014
1015        // TODO: Step 3. Unblock rendering on el.
1016        let mut script = match result {
1017            // Step 4. If el's result is null, then fire an event named error at el, and return.
1018            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        // Step 5.
1033        // If el's from an external file is true, or el's type is "module", then increment document's
1034        // ignore-destructive-writes counter.
1035        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        // Step 6.
1045        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                // Step 6.1 Register an import map given el's relevant global object and el's result.
1072                register_import_map(&self.owner_global(), script.import_map, can_gc);
1073            },
1074        }
1075
1076        // Step 7.
1077        // Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step.
1078        if let Some(doc) = neutralized_doc {
1079            doc.decr_ignore_destructive_writes_counter();
1080        }
1081
1082        // Step 8. If el's from an external file is true, then fire an event named load at el.
1083        if script.external {
1084            self.dispatch_load_event(can_gc);
1085        }
1086    }
1087
1088    // https://html.spec.whatwg.org/multipage/#run-a-classic-script
1089    pub(crate) fn run_a_classic_script(
1090        &self,
1091        script: &ScriptOrigin,
1092        can_gc: CanGc,
1093        introduction_type: Option<&'static CStr>,
1094    ) {
1095        // TODO use a settings object rather than this element's document/window
1096        // Step 2
1097        let document = self.owner_document();
1098        if !document.is_fully_active() || !document.scripting_enabled() {
1099            return;
1100        }
1101
1102        // Steps 4-10
1103        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    /// <https://html.spec.whatwg.org/multipage/#run-a-module-script>
1126    pub(crate) fn run_a_module_script(
1127        &self,
1128        script: &ScriptOrigin,
1129        _rethrow_errors: bool,
1130        can_gc: CanGc,
1131    ) {
1132        // TODO use a settings object rather than this element's document/window
1133        // Step 2
1134        let document = self.owner_document();
1135        if !document.is_fully_active() || !document.scripting_enabled() {
1136            return;
1137        }
1138
1139        // Step 4
1140        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            // Step 6.
1156            {
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    // https://html.spec.whatwg.org/multipage/#prepare-a-script Step 7.
1210    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    /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-26>
1321    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            // This method can be invoked while there are script/layout blockers present
1329            // as DOM mutations have not yet settled. We use a delayed task to avoid
1330            // running any scripts until the DOM tree is safe for interactions.
1331            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    /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-20>
1340    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        // https://html.spec.whatwg.org/multipage/#already-started
1362        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    // https://html.spec.whatwg.org/multipage/#dom-script-src
1372    fn Src(&self) -> TrustedScriptURLOrUSVString {
1373        let element = self.upcast::<Element>();
1374        element.get_trusted_type_url_attribute(&local_name!("src"))
1375    }
1376
1377    /// <https://w3c.github.io/trusted-types/dist/spec/#the-src-idl-attribute>
1378    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    // https://html.spec.whatwg.org/multipage/#dom-script-type
1397    make_getter!(Type, "type");
1398    // https://html.spec.whatwg.org/multipage/#dom-script-type
1399    make_setter!(SetType, "type");
1400
1401    // https://html.spec.whatwg.org/multipage/#dom-script-charset
1402    make_getter!(Charset, "charset");
1403    // https://html.spec.whatwg.org/multipage/#dom-script-charset
1404    make_setter!(SetCharset, "charset");
1405
1406    // https://html.spec.whatwg.org/multipage/#dom-script-async
1407    fn Async(&self) -> bool {
1408        self.non_blocking.get() ||
1409            self.upcast::<Element>()
1410                .has_attribute(&local_name!("async"))
1411    }
1412
1413    // https://html.spec.whatwg.org/multipage/#dom-script-async
1414    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    // https://html.spec.whatwg.org/multipage/#dom-script-defer
1421    make_bool_getter!(Defer, "defer");
1422    // https://html.spec.whatwg.org/multipage/#dom-script-defer
1423    make_bool_setter!(SetDefer, "defer");
1424
1425    // https://html.spec.whatwg.org/multipage/#dom-script-nomodule
1426    make_bool_getter!(NoModule, "nomodule");
1427    // https://html.spec.whatwg.org/multipage/#dom-script-nomodule
1428    make_bool_setter!(SetNoModule, "nomodule");
1429
1430    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
1431    make_getter!(Integrity, "integrity");
1432    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
1433    make_setter!(SetIntegrity, "integrity");
1434
1435    // https://html.spec.whatwg.org/multipage/#dom-script-event
1436    make_getter!(Event, "event");
1437    // https://html.spec.whatwg.org/multipage/#dom-script-event
1438    make_setter!(SetEvent, "event");
1439
1440    // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
1441    make_getter!(HtmlFor, "for");
1442    // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
1443    make_setter!(SetHtmlFor, "for");
1444
1445    // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin
1446    fn GetCrossOrigin(&self) -> Option<DOMString> {
1447        reflect_cross_origin_attribute(self.upcast::<Element>())
1448    }
1449
1450    // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin
1451    fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1452        set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1453    }
1454
1455    // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
1456    fn ReferrerPolicy(&self) -> DOMString {
1457        reflect_referrer_policy_attribute(self.upcast::<Element>())
1458    }
1459
1460    // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
1461    make_setter!(SetReferrerPolicy, "referrerpolicy");
1462
1463    /// <https://w3c.github.io/trusted-types/dist/spec/#dom-htmlscriptelement-innertext>
1464    fn InnerText(&self) -> TrustedScriptOrString {
1465        // Step 1: Return the result of running get the text steps with this.
1466        TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1467    }
1468
1469    /// <https://w3c.github.io/trusted-types/dist/spec/#the-innerText-idl-attribute>
1470    fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1471        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1472        // this's relevant global object, the given value, HTMLScriptElement innerText, and script.
1473        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        // Step 3: Run set the inner text steps with this and value.
1481        self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1482        Ok(())
1483    }
1484
1485    /// <https://html.spec.whatwg.org/multipage/#dom-script-text>
1486    fn Text(&self) -> TrustedScriptOrString {
1487        TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1488    }
1489
1490    /// <https://w3c.github.io/trusted-types/dist/spec/#the-text-idl-attribute>
1491    fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1492        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1493        // this's relevant global object, the given value, HTMLScriptElement text, and script.
1494        let value = TrustedScript::get_trusted_script_compliant_string(
1495            &self.owner_global(),
1496            value,
1497            "HTMLScriptElement text",
1498            can_gc,
1499        )?;
1500        // Step 2: Set this's script text value to the given value.
1501        *self.script_text.borrow_mut() = value.clone();
1502        // Step 3: String replace all with the given value within this.
1503        Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1504        Ok(())
1505    }
1506
1507    /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute>
1508    fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1509        // Step 1: Return the result of running get text content with this.
1510        Some(TrustedScriptOrString::String(
1511            self.upcast::<Node>().GetTextContent()?,
1512        ))
1513    }
1514
1515    /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute>
1516    fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1517        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1518        // this's relevant global object, the given value, HTMLScriptElement textContent, and script.
1519        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        // Step 2: Set this's script text value to value.
1526        *self.script_text.borrow_mut() = value.clone();
1527        // Step 3: Run set text content with this and value.
1528        self.upcast::<Node>()
1529            .set_text_content_for_element(Some(value), can_gc);
1530        Ok(())
1531    }
1532
1533    /// <https://html.spec.whatwg.org/multipage/#dom-script-supports>
1534    fn Supports(_window: &Window, type_: DOMString) -> bool {
1535        // The type argument has to exactly match these values,
1536        // we do not perform an ASCII case-insensitive match.
1537        matches!(&*type_.str(), "classic" | "module" | "importmap")
1538    }
1539}
1540
1541#[derive(Clone, Copy)]
1542enum ExternalScriptKind {
1543    Deferred,
1544    ParsingBlocking,
1545    AsapInOrder,
1546    Asap,
1547}