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 servo_url::{ImmutableOrigin, ServoUrl};
27use style::attr::AttrValue;
28use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
29use stylo_atoms::Atom;
30use uuid::Uuid;
31
32use crate::document_loader::LoadType;
33use crate::dom::attr::Attr;
34use crate::dom::bindings::cell::DomRefCell;
35use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
36use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
37use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
38use crate::dom::bindings::codegen::UnionTypes::{
39    TrustedScriptOrString, TrustedScriptURLOrUSVString,
40};
41use crate::dom::bindings::error::{Error, Fallible};
42use crate::dom::bindings::inheritance::Castable;
43use crate::dom::bindings::refcounted::Trusted;
44use crate::dom::bindings::reflector::DomGlobal;
45use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
46use crate::dom::bindings::settings_stack::AutoEntryScript;
47use crate::dom::bindings::str::DOMString;
48use crate::dom::bindings::trace::NoTrace;
49use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
50use crate::dom::document::Document;
51use crate::dom::element::{
52    AttributeMutation, Element, ElementCreator, cors_setting_for_element,
53    referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
54    set_cross_origin_attribute,
55};
56use crate::dom::event::{Event, EventBubbles, EventCancelable};
57use crate::dom::globalscope::GlobalScope;
58use crate::dom::html::htmlelement::HTMLElement;
59use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits};
60use crate::dom::performanceresourcetiming::InitiatorType;
61use crate::dom::trustedscript::TrustedScript;
62use crate::dom::trustedscripturl::TrustedScriptURL;
63use crate::dom::virtualmethods::VirtualMethods;
64use crate::dom::window::Window;
65use crate::fetch::create_a_potential_cors_request;
66use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
67use crate::realms::enter_realm;
68use crate::script_module::{
69    ImportMap, ModuleOwner, ScriptFetchOptions, fetch_external_module_script,
70    fetch_inline_module_script, parse_an_import_map_string, register_import_map,
71};
72use crate::script_runtime::{CanGc, IntroductionType};
73use crate::unminify::{ScriptSource, unminify_js};
74
75impl ScriptSource for ScriptOrigin {
76    fn unminified_dir(&self) -> Option<String> {
77        self.unminified_dir.clone()
78    }
79
80    fn extract_bytes(&self) -> &[u8] {
81        match &self.code {
82            SourceCode::Text(text) => text.as_bytes(),
83            SourceCode::Compiled(compiled_source_code) => {
84                compiled_source_code.original_text.as_bytes()
85            },
86        }
87    }
88
89    fn rewrite_source(&mut self, source: Rc<DOMString>) {
90        self.code = SourceCode::Text(source);
91    }
92
93    fn url(&self) -> ServoUrl {
94        self.url.clone()
95    }
96
97    fn is_external(&self) -> bool {
98        self.external
99    }
100}
101
102/// An unique id for script element.
103#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
104pub(crate) struct ScriptId(#[no_trace] Uuid);
105
106#[dom_struct]
107pub(crate) struct HTMLScriptElement {
108    htmlelement: HTMLElement,
109
110    /// <https://html.spec.whatwg.org/multipage/#already-started>
111    already_started: Cell<bool>,
112
113    /// <https://html.spec.whatwg.org/multipage/#parser-inserted>
114    parser_inserted: Cell<bool>,
115
116    /// <https://html.spec.whatwg.org/multipage/#non-blocking>
117    ///
118    /// (currently unused)
119    non_blocking: Cell<bool>,
120
121    /// Document of the parser that created this element
122    /// <https://html.spec.whatwg.org/multipage/#parser-document>
123    parser_document: Dom<Document>,
124
125    /// Prevents scripts that move between documents during preparation from executing.
126    /// <https://html.spec.whatwg.org/multipage/#preparation-time-document>
127    preparation_time_document: MutNullableDom<Document>,
128
129    /// Track line line_number
130    line_number: u64,
131
132    /// Unique id for each script element
133    #[ignore_malloc_size_of = "Defined in uuid"]
134    id: ScriptId,
135
136    /// <https://w3c.github.io/trusted-types/dist/spec/#htmlscriptelement-script-text>
137    script_text: DomRefCell<DOMString>,
138
139    /// `introductionType` value to set in the `CompileOptionsWrapper`, overriding the usual
140    /// `srcScript` or `inlineScript` that this script would normally use.
141    #[no_trace]
142    introduction_type_override: Cell<Option<&'static CStr>>,
143}
144
145impl HTMLScriptElement {
146    fn new_inherited(
147        local_name: LocalName,
148        prefix: Option<Prefix>,
149        document: &Document,
150        creator: ElementCreator,
151    ) -> HTMLScriptElement {
152        HTMLScriptElement {
153            id: ScriptId(Uuid::new_v4()),
154            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
155            already_started: Cell::new(false),
156            parser_inserted: Cell::new(creator.is_parser_created()),
157            non_blocking: Cell::new(!creator.is_parser_created()),
158            parser_document: Dom::from_ref(document),
159            preparation_time_document: MutNullableDom::new(None),
160            line_number: creator.return_line_number(),
161            script_text: DomRefCell::new(DOMString::new()),
162            introduction_type_override: Cell::new(None),
163        }
164    }
165
166    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
167    pub(crate) fn new(
168        local_name: LocalName,
169        prefix: Option<Prefix>,
170        document: &Document,
171        proto: Option<HandleObject>,
172        creator: ElementCreator,
173        can_gc: CanGc,
174    ) -> DomRoot<HTMLScriptElement> {
175        Node::reflect_node_with_proto(
176            Box::new(HTMLScriptElement::new_inherited(
177                local_name, prefix, document, creator,
178            )),
179            document,
180            proto,
181            can_gc,
182        )
183    }
184
185    pub(crate) fn get_script_id(&self) -> ScriptId {
186        self.id
187    }
188}
189
190/// Supported script types as defined by
191/// <https://html.spec.whatwg.org/multipage/#javascript-mime-type>.
192pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[
193    "application/ecmascript",
194    "application/javascript",
195    "application/x-ecmascript",
196    "application/x-javascript",
197    "text/ecmascript",
198    "text/javascript",
199    "text/javascript1.0",
200    "text/javascript1.1",
201    "text/javascript1.2",
202    "text/javascript1.3",
203    "text/javascript1.4",
204    "text/javascript1.5",
205    "text/jscript",
206    "text/livescript",
207    "text/x-ecmascript",
208    "text/x-javascript",
209];
210
211#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
212pub(crate) enum ScriptType {
213    Classic,
214    Module,
215    ImportMap,
216}
217
218#[derive(JSTraceable, MallocSizeOf)]
219pub(crate) struct CompiledSourceCode {
220    #[ignore_malloc_size_of = "SM handles JS values"]
221    pub(crate) source_code: Stencil,
222    #[conditional_malloc_size_of = "Rc is hard"]
223    pub(crate) original_text: Rc<DOMString>,
224}
225
226#[derive(JSTraceable, MallocSizeOf)]
227pub(crate) enum SourceCode {
228    Text(#[conditional_malloc_size_of] Rc<DOMString>),
229    Compiled(CompiledSourceCode),
230}
231
232#[derive(JSTraceable, MallocSizeOf)]
233pub(crate) struct ScriptOrigin {
234    code: SourceCode,
235    #[no_trace]
236    url: ServoUrl,
237    external: bool,
238    fetch_options: ScriptFetchOptions,
239    type_: ScriptType,
240    unminified_dir: Option<String>,
241    import_map: Fallible<ImportMap>,
242}
243
244impl ScriptOrigin {
245    pub(crate) fn internal(
246        text: Rc<DOMString>,
247        url: ServoUrl,
248        fetch_options: ScriptFetchOptions,
249        type_: ScriptType,
250        unminified_dir: Option<String>,
251        import_map: Fallible<ImportMap>,
252    ) -> ScriptOrigin {
253        ScriptOrigin {
254            code: SourceCode::Text(text),
255            url,
256            external: false,
257            fetch_options,
258            type_,
259            unminified_dir,
260            import_map,
261        }
262    }
263
264    pub(crate) fn external(
265        text: Rc<DOMString>,
266        url: ServoUrl,
267        fetch_options: ScriptFetchOptions,
268        type_: ScriptType,
269        unminified_dir: Option<String>,
270    ) -> ScriptOrigin {
271        ScriptOrigin {
272            code: SourceCode::Text(text),
273            url,
274            external: true,
275            fetch_options,
276            type_,
277            unminified_dir,
278            import_map: Err(Error::NotFound),
279        }
280    }
281
282    pub(crate) fn text(&self) -> Rc<DOMString> {
283        match &self.code {
284            SourceCode::Text(text) => Rc::clone(text),
285            SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text),
286        }
287    }
288}
289
290/// Final steps of <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
291fn finish_fetching_a_classic_script(
292    elem: &HTMLScriptElement,
293    script_kind: ExternalScriptKind,
294    url: ServoUrl,
295    load: ScriptResult,
296    can_gc: CanGc,
297) {
298    // Step 33. The "steps to run when the result is ready" for each type of script in 33.2-33.5.
299    // of https://html.spec.whatwg.org/multipage/#prepare-the-script-element
300    let document;
301
302    match script_kind {
303        ExternalScriptKind::Asap => {
304            document = elem.preparation_time_document.get().unwrap();
305            document.asap_script_loaded(elem, load, can_gc)
306        },
307        ExternalScriptKind::AsapInOrder => {
308            document = elem.preparation_time_document.get().unwrap();
309            document.asap_in_order_script_loaded(elem, load, can_gc)
310        },
311        ExternalScriptKind::Deferred => {
312            document = elem.parser_document.as_rooted();
313            document.deferred_script_loaded(elem, load, can_gc);
314        },
315        ExternalScriptKind::ParsingBlocking => {
316            document = elem.parser_document.as_rooted();
317            document.pending_parsing_blocking_script_loaded(elem, load, can_gc);
318        },
319    }
320
321    document.finish_load(LoadType::Script(url), can_gc);
322}
323
324pub(crate) type ScriptResult = Result<ScriptOrigin, NoTrace<NetworkError>>;
325
326/// The context required for asynchronously loading an external script source.
327struct ClassicContext {
328    /// The element that initiated the request.
329    elem: Trusted<HTMLScriptElement>,
330    /// The kind of external script.
331    kind: ExternalScriptKind,
332    /// The (fallback) character encoding argument to the "fetch a classic
333    /// script" algorithm.
334    character_encoding: &'static Encoding,
335    /// The response body received to date.
336    data: Vec<u8>,
337    /// The response metadata received to date.
338    metadata: Option<Metadata>,
339    /// The initial URL requested.
340    url: ServoUrl,
341    /// Indicates whether the request failed, and why
342    status: Result<(), NetworkError>,
343    /// The fetch options of the script
344    fetch_options: ScriptFetchOptions,
345    /// Timing object for this resource
346    resource_timing: ResourceFetchTiming,
347}
348
349impl FetchResponseListener for ClassicContext {
350    // TODO(KiChjang): Perhaps add custom steps to perform fetch here?
351    fn process_request_body(&mut self, _: RequestId) {}
352
353    // TODO(KiChjang): Perhaps add custom steps to perform fetch here?
354    fn process_request_eof(&mut self, _: RequestId) {}
355
356    fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
357        self.metadata = metadata.ok().map(|meta| match meta {
358            FetchMetadata::Unfiltered(m) => m,
359            FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
360        });
361
362        let status = self
363            .metadata
364            .as_ref()
365            .map(|m| m.status.clone())
366            .unwrap_or_else(HttpStatus::new_error);
367
368        self.status = {
369            if status.is_error() {
370                Err(NetworkError::Internal(
371                    "No http status code received".to_owned(),
372                ))
373            } else if status.is_success() {
374                Ok(())
375            } else {
376                Err(NetworkError::Internal(format!(
377                    "HTTP error code {}",
378                    status.code()
379                )))
380            }
381        };
382    }
383
384    fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
385        if self.status.is_ok() {
386            self.data.append(&mut chunk);
387        }
388    }
389
390    /// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
391    /// step 4-9
392    #[allow(unsafe_code)]
393    fn process_response_eof(
394        &mut self,
395        _: RequestId,
396        response: Result<ResourceFetchTiming, NetworkError>,
397    ) {
398        let (source_text, final_url) = match (response.as_ref(), self.status.as_ref()) {
399            (Err(err), _) | (_, Err(err)) => {
400                // Step 6, response is an error.
401                finish_fetching_a_classic_script(
402                    &self.elem.root(),
403                    self.kind,
404                    self.url.clone(),
405                    Err(NoTrace(err.clone())),
406                    CanGc::note(),
407                );
408                return;
409            },
410            (Ok(_), Ok(_)) => {
411                let metadata = self.metadata.take().unwrap();
412
413                // Step 7.
414                let encoding = metadata
415                    .charset
416                    .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
417                    .unwrap_or(self.character_encoding);
418
419                // Step 8.
420                let (source_text, _, _) = encoding.decode(&self.data);
421                (source_text, metadata.final_url)
422            },
423        };
424
425        let elem = self.elem.root();
426        let global = elem.global();
427        // let cx = GlobalScope::get_cx();
428        let _ar = enter_realm(&*global);
429
430        /*
431        let options = unsafe { CompileOptionsWrapper::new(*cx, final_url.as_str(), 1) };
432
433        let can_compile_off_thread = pref!(dom_script_asynch) &&
434            unsafe { CanCompileOffThread(*cx, options.ptr as *const _, source_text.len()) };
435
436        if can_compile_off_thread {
437            let source_string = source_text.to_string();
438
439            let context = Box::new(OffThreadCompilationContext {
440                script_element: self.elem.clone(),
441                script_kind: self.kind,
442                final_url,
443                url: self.url.clone(),
444                task_source: elem.owner_global().task_manager().dom_manipulation_task_source(),
445                script_text: source_string,
446                fetch_options: self.fetch_options.clone(),
447            });
448
449            unsafe {
450                assert!(!CompileToStencilOffThread1(
451                    *cx,
452                    options.ptr as *const _,
453                    &mut transform_str_to_source_text(&context.script_text) as *mut _,
454                    Some(off_thread_compilation_callback),
455                    Box::into_raw(context) as *mut c_void,
456                )
457                .is_null());
458            }
459        } else {*/
460        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        // }
475    }
476
477    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
478        &mut self.resource_timing
479    }
480
481    fn resource_timing(&self) -> &ResourceFetchTiming {
482        &self.resource_timing
483    }
484
485    fn submit_resource_timing(&mut self) {
486        network_listener::submit_timing(self, CanGc::note())
487    }
488
489    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
490        let global = &self.resource_timing_global();
491        let elem = self.elem.root();
492        let source_position = elem
493            .upcast::<Element>()
494            .compute_source_position(elem.line_number as u32);
495        global.report_csp_violations(violations, Some(elem.upcast()), Some(source_position));
496    }
497}
498
499impl ResourceTimingListener for ClassicContext {
500    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
501        let initiator_type = InitiatorType::LocalName(
502            self.elem
503                .root()
504                .upcast::<Element>()
505                .local_name()
506                .to_string(),
507        );
508        (initiator_type, self.url.clone())
509    }
510
511    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
512        self.elem.root().owner_document().global()
513    }
514}
515
516impl PreInvoke for ClassicContext {}
517
518/// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
519// This function is also used to prefetch a script in `script::dom::servoparser::prefetch`.
520#[allow(clippy::too_many_arguments)]
521pub(crate) fn script_fetch_request(
522    webview_id: WebViewId,
523    url: ServoUrl,
524    cors_setting: Option<CorsSettings>,
525    origin: ImmutableOrigin,
526    pipeline_id: PipelineId,
527    options: ScriptFetchOptions,
528    insecure_requests_policy: InsecureRequestsPolicy,
529    has_trustworthy_ancestor_origin: bool,
530    policy_container: PolicyContainer,
531) -> RequestBuilder {
532    // We intentionally ignore options' credentials_mode member for classic scripts.
533    // The mode is initialized by create_a_potential_cors_request.
534    create_a_potential_cors_request(
535        Some(webview_id),
536        url,
537        Destination::Script,
538        cors_setting,
539        None,
540        options.referrer,
541        insecure_requests_policy,
542        has_trustworthy_ancestor_origin,
543        policy_container,
544    )
545    .origin(origin)
546    .pipeline_id(Some(pipeline_id))
547    .parser_metadata(options.parser_metadata)
548    .integrity_metadata(options.integrity_metadata.clone())
549    .referrer_policy(options.referrer_policy)
550    .cryptographic_nonce_metadata(options.cryptographic_nonce)
551}
552
553/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
554fn fetch_a_classic_script(
555    script: &HTMLScriptElement,
556    kind: ExternalScriptKind,
557    url: ServoUrl,
558    cors_setting: Option<CorsSettings>,
559    options: ScriptFetchOptions,
560    character_encoding: &'static Encoding,
561) {
562    // Step 1, 2.
563    let doc = script.owner_document();
564    let global = script.global();
565    let request = script_fetch_request(
566        doc.webview_id(),
567        url.clone(),
568        cors_setting,
569        doc.origin().immutable().clone(),
570        global.pipeline_id(),
571        options.clone(),
572        doc.insecure_requests_policy(),
573        doc.has_trustworthy_ancestor_origin(),
574        global.policy_container(),
575    );
576    let request = doc.prepare_request(request);
577
578    // TODO: Step 3, Add custom steps to perform fetch
579
580    let context = ClassicContext {
581        elem: Trusted::new(script),
582        kind,
583        character_encoding,
584        data: vec![],
585        metadata: None,
586        url: url.clone(),
587        status: Ok(()),
588        fetch_options: options,
589        resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
590    };
591    doc.fetch(LoadType::Script(url), request, context);
592}
593
594impl HTMLScriptElement {
595    /// <https://w3c.github.io/trusted-types/dist/spec/#setting-slot-values-from-parser>
596    pub(crate) fn set_initial_script_text(&self) {
597        *self.script_text.borrow_mut() = self.text();
598    }
599
600    /// <https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-prepare-the-script-text>
601    fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> {
602        // Step 1. If script’s script text value is not equal to its child text content,
603        // set script’s script text to the result of executing
604        // Get Trusted Type compliant string, with the following arguments:
605        if self.script_text.borrow().clone() != self.text() {
606            *self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string(
607                &self.owner_global(),
608                self.Text(),
609                "HTMLScriptElement text",
610                can_gc,
611            )?;
612        }
613
614        Ok(())
615    }
616
617    /// <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
618    pub(crate) fn prepare(&self, introduction_type_override: Option<&'static CStr>, can_gc: CanGc) {
619        self.introduction_type_override
620            .set(introduction_type_override);
621
622        // Step 1. If el's already started is true, then return.
623        if self.already_started.get() {
624            return;
625        }
626
627        // Step 2. Let parser document be el's parser document.
628        // TODO
629
630        // Step 3. Set el's parser document to null.
631        let was_parser_inserted = self.parser_inserted.get();
632        self.parser_inserted.set(false);
633
634        // Step 4.
635        // If parser document is non-null and el does not have an async attribute, then set el's force async to true.
636        let element = self.upcast::<Element>();
637        let asynch = element.has_attribute(&local_name!("async"));
638        // Note: confusingly, this is done if the element does *not* have an "async" attribute.
639        if was_parser_inserted && !asynch {
640            self.non_blocking.set(true);
641        }
642
643        // Step 5. Execute the Prepare the script text algorithm on el.
644        // If that algorithm threw an error, then return.
645        if self.prepare_the_script_text(can_gc).is_err() {
646            return;
647        }
648        // Step 5a. Let source text be el’s script text value.
649        let text = self.script_text.borrow().clone();
650        // Step 6. If el has no src attribute, and source text is the empty string, then return.
651        if text.is_empty() && !element.has_attribute(&local_name!("src")) {
652            return;
653        }
654
655        // Step 7. If el is not connected, then return.
656        if !self.upcast::<Node>().is_connected() {
657            return;
658        }
659
660        let script_type = if let Some(ty) = self.get_script_type() {
661            // Step 9-11.
662            ty
663        } else {
664            // Step 12. Otherwise, return. (No script is executed, and el's type is left as null.)
665            return;
666        };
667
668        // Step 13.
669        // If parser document is non-null, then set el's parser document back to parser document and set el's force
670        // async to false.
671        if was_parser_inserted {
672            self.parser_inserted.set(true);
673            self.non_blocking.set(false);
674        }
675
676        // Step 14. Set el's already started to true.
677        self.already_started.set(true);
678
679        // Step 15. Set el's preparation-time document to its node document.
680        let doc = self.owner_document();
681        self.preparation_time_document.set(Some(&doc));
682
683        // Step 16.
684        // If parser document is non-null, and parser document is not equal to el's preparation-time document, then
685        // return.
686        if self.parser_inserted.get() && *self.parser_document != *doc {
687            return;
688        }
689
690        // Step 17. If scripting is disabled for el, then return.
691        if !doc.is_scripting_enabled() {
692            return;
693        }
694
695        // Step 18. If el has a nomodule content attribute and its type is "classic", then return.
696        if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
697            return;
698        }
699
700        let global = &doc.global();
701
702        // Step 19. CSP.
703        if !element.has_attribute(&local_name!("src")) &&
704            global
705                .get_csp_list()
706                .should_elements_inline_type_behavior_be_blocked(
707                    global,
708                    element,
709                    InlineCheckType::Script,
710                    &text,
711                )
712        {
713            warn!("Blocking inline script due to CSP");
714            return;
715        }
716
717        // Step 20. If el has an event attribute and a for attribute, and el's type is "classic", then:
718        if script_type == ScriptType::Classic {
719            let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
720            let event_attribute = element.get_attribute(&ns!(), &local_name!("event"));
721            if let (Some(ref for_attribute), Some(ref event_attribute)) =
722                (for_attribute, event_attribute)
723            {
724                let for_value = for_attribute.value().to_ascii_lowercase();
725                let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
726                if for_value != "window" {
727                    return;
728                }
729
730                let event_value = event_attribute.value().to_ascii_lowercase();
731                let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
732                if event_value != "onload" && event_value != "onload()" {
733                    return;
734                }
735            }
736        }
737
738        // Step 21. If el has a charset attribute, then let encoding be the result of getting
739        // an encoding from the value of the charset attribute.
740        // If el does not have a charset attribute, or if getting an encoding failed,
741        // then let encoding be el's node document's the encoding.
742        let encoding = element
743            .get_attribute(&ns!(), &local_name!("charset"))
744            .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
745            .unwrap_or_else(|| doc.encoding());
746
747        // Step 22. CORS setting.
748        let cors_setting = cors_setting_for_element(element);
749
750        // Step 23. Module script credentials mode.
751        let module_credentials_mode = match script_type {
752            ScriptType::Classic => CredentialsMode::CredentialsSameOrigin,
753            ScriptType::Module | ScriptType::ImportMap => reflect_cross_origin_attribute(element)
754                .map_or(
755                    CredentialsMode::CredentialsSameOrigin,
756                    |attr| match &*attr {
757                        "use-credentials" => CredentialsMode::Include,
758                        "anonymous" => CredentialsMode::CredentialsSameOrigin,
759                        _ => CredentialsMode::CredentialsSameOrigin,
760                    },
761                ),
762        };
763
764        // Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
765        let cryptographic_nonce = self.upcast::<Element>().nonce_value();
766
767        // Step 25. If el has an integrity attribute, then let integrity metadata be that attribute's value.
768        // Otherwise, let integrity metadata be the empty string.
769        let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
770        let integrity_val = im_attribute.as_ref().map(|a| a.value());
771        let integrity_metadata = match integrity_val {
772            Some(ref value) => &***value,
773            None => "",
774        };
775
776        // Step 26. Let referrer policy be the current state of el's referrerpolicy content attribute.
777        let referrer_policy = referrer_policy_for_element(self.upcast::<Element>());
778
779        // TODO: Step 27. Fetch priority.
780
781        // Step 28. Let parser metadata be "parser-inserted" if el is parser-inserted,
782        // and "not-parser-inserted" otherwise.
783        let parser_metadata = if self.parser_inserted.get() {
784            ParserMetadata::ParserInserted
785        } else {
786            ParserMetadata::NotParserInserted
787        };
788
789        // Step 29. Fetch options.
790        let options = ScriptFetchOptions {
791            cryptographic_nonce,
792            integrity_metadata: integrity_metadata.to_owned(),
793            parser_metadata,
794            referrer: self.global().get_referrer(),
795            referrer_policy,
796            credentials_mode: module_credentials_mode,
797        };
798
799        // Step 30. Let settings object be el's node document's relevant settings object.
800        // This is done by passing ModuleOwner in step 31.11 and step 32.2.
801        // What we actually need is global's import map eventually.
802
803        let base_url = doc.base_url();
804        if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
805            // Step 31. If el has a src content attribute, then:
806
807            // Step 31.1. If el's type is "importmap".
808            if script_type == ScriptType::ImportMap {
809                // then queue an element task on the DOM manipulation task source
810                // given el to fire an event named error at el, and return.
811                self.queue_error_event();
812                return;
813            }
814
815            // Step 31.2. Let src be the value of el's src attribute.
816            let src = src.value();
817
818            // Step 31.3. If src is the empty string.
819            if src.is_empty() {
820                self.queue_error_event();
821                return;
822            }
823
824            // Step 31.4. Set el's from an external file to true.
825            // The "from an external file"" flag is stored in ScriptOrigin.
826
827            // Step 31.5-31.6. Parse URL.
828            let url = match base_url.join(&src) {
829                Ok(url) => url,
830                Err(_) => {
831                    warn!("error parsing URL for script {}", &**src);
832                    self.queue_error_event();
833                    return;
834                },
835            };
836
837            // TODO:
838            // Step 31.7. If el is potentially render-blocking, then block rendering on el.
839            // Step 31.8. Set el's delaying the load event to true.
840            // Step 31.9. If el is currently render-blocking, then set options's render-blocking to true.
841
842            // Step 31.11. Switch on el's type:
843            match script_type {
844                ScriptType::Classic => {
845                    let kind = if element.has_attribute(&local_name!("defer")) &&
846                        was_parser_inserted &&
847                        !asynch
848                    {
849                        // Step 33.4: classic, has src, has defer, was parser-inserted, is not async.
850                        ExternalScriptKind::Deferred
851                    } else if was_parser_inserted && !asynch {
852                        // Step 33.5: classic, has src, was parser-inserted, is not async.
853                        ExternalScriptKind::ParsingBlocking
854                    } else if !asynch && !self.non_blocking.get() {
855                        // Step 33.3: classic, has src, is not async, is not non-blocking.
856                        ExternalScriptKind::AsapInOrder
857                    } else {
858                        // Step 33.2: classic, has src.
859                        ExternalScriptKind::Asap
860                    };
861
862                    // Step 31.11. Fetch a classic script.
863                    fetch_a_classic_script(self, kind, url, cors_setting, options, encoding);
864
865                    // Step 33.2/33.3/33.4/33.5, substeps 1-2. Add el to the corresponding script list.
866                    match kind {
867                        ExternalScriptKind::Deferred => doc.add_deferred_script(self),
868                        ExternalScriptKind::ParsingBlocking => {
869                            doc.set_pending_parsing_blocking_script(self, None)
870                        },
871                        ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
872                        ExternalScriptKind::Asap => doc.add_asap_script(self),
873                    }
874                },
875                ScriptType::Module => {
876                    // Step 31.11. Fetch an external module script graph.
877                    fetch_external_module_script(
878                        ModuleOwner::Window(Trusted::new(self)),
879                        url.clone(),
880                        Destination::Script,
881                        options,
882                        can_gc,
883                    );
884
885                    if !asynch && was_parser_inserted {
886                        // 33.4: module, not async, parser-inserted
887                        doc.add_deferred_script(self);
888                    } else if !asynch && !self.non_blocking.get() {
889                        // 33.3: module, not parser-inserted
890                        doc.push_asap_in_order_script(self);
891                    } else {
892                        // 33.2: module, async
893                        doc.add_asap_script(self);
894                    };
895                },
896                ScriptType::ImportMap => (),
897            }
898        } else {
899            // Step 32. If el does not have a src content attribute:
900
901            assert!(!text.is_empty());
902
903            let text_rc = Rc::new(text);
904
905            // Step 32.2: Switch on el's type:
906            match script_type {
907                ScriptType::Classic => {
908                    let result = Ok(ScriptOrigin::internal(
909                        text_rc,
910                        base_url,
911                        options,
912                        script_type,
913                        self.global().unminified_js_dir(),
914                        Err(Error::NotFound),
915                    ));
916
917                    if was_parser_inserted &&
918                        doc.get_current_parser()
919                            .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
920                        doc.get_script_blocking_stylesheets_count() > 0
921                    {
922                        // Step 34.2: classic, has no src, was parser-inserted, is blocked on stylesheet.
923                        doc.set_pending_parsing_blocking_script(self, Some(result));
924                    } else {
925                        // Step 34.3: otherwise.
926                        self.execute(result, can_gc);
927                    }
928                },
929                ScriptType::Module => {
930                    // We should add inline module script elements
931                    // into those vectors in case that there's no
932                    // descendants in the inline module script.
933                    if !asynch && was_parser_inserted {
934                        doc.add_deferred_script(self);
935                    } else if !asynch && !self.non_blocking.get() {
936                        doc.push_asap_in_order_script(self);
937                    } else {
938                        doc.add_asap_script(self);
939                    };
940
941                    fetch_inline_module_script(
942                        ModuleOwner::Window(Trusted::new(self)),
943                        text_rc,
944                        base_url.clone(),
945                        self.id,
946                        options,
947                        can_gc,
948                    );
949                },
950                ScriptType::ImportMap => {
951                    // Step 32.1 Let result be the result of creating an import map
952                    // parse result given source text and base URL.
953                    let import_map_result = parse_an_import_map_string(
954                        ModuleOwner::Window(Trusted::new(self)),
955                        Rc::clone(&text_rc),
956                        base_url.clone(),
957                        can_gc,
958                    );
959                    let result = Ok(ScriptOrigin::internal(
960                        text_rc,
961                        base_url,
962                        options,
963                        script_type,
964                        self.global().unminified_js_dir(),
965                        import_map_result,
966                    ));
967
968                    // Step 34.3
969                    self.execute(result, can_gc);
970                },
971            }
972        }
973    }
974
975    fn substitute_with_local_script(&self, script: &mut ScriptOrigin) {
976        if self
977            .parser_document
978            .window()
979            .local_script_source()
980            .is_none() ||
981            !script.external
982        {
983            return;
984        }
985        let mut path = PathBuf::from(
986            self.parser_document
987                .window()
988                .local_script_source()
989                .clone()
990                .unwrap(),
991        );
992        path = path.join(&script.url[url::Position::BeforeHost..]);
993        debug!("Attempting to read script stored at: {:?}", path);
994        match read_to_string(path.clone()) {
995            Ok(local_script) => {
996                debug!("Found script stored at: {:?}", path);
997                script.code = SourceCode::Text(Rc::new(DOMString::from(local_script)));
998            },
999            Err(why) => warn!("Could not restore script from file {:?}", why),
1000        }
1001    }
1002
1003    /// <https://html.spec.whatwg.org/multipage/#execute-the-script-element>
1004    pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) {
1005        // Step 1. Let document be el's node document.
1006        let doc = self.owner_document();
1007
1008        // Step 2. If el's preparation-time document is not equal to document, then return.
1009        if *doc != *self.preparation_time_document.get().unwrap() {
1010            return;
1011        }
1012
1013        // TODO: Step 3. Unblock rendering on el.
1014        let mut script = match result {
1015            // Step 4. If el's result is null, then fire an event named error at el, and return.
1016            Err(e) => {
1017                warn!("error loading script {:?}", e);
1018                self.dispatch_error_event(can_gc);
1019                return;
1020            },
1021
1022            Ok(script) => script,
1023        };
1024
1025        if script.type_ == ScriptType::Classic {
1026            unminify_js(&mut script);
1027            self.substitute_with_local_script(&mut script);
1028        }
1029
1030        // Step 5.
1031        // If el's from an external file is true, or el's type is "module", then increment document's
1032        // ignore-destructive-writes counter.
1033        let neutralized_doc = if script.external || script.type_ == ScriptType::Module {
1034            debug!("loading external script, url = {}", script.url);
1035            let doc = self.owner_document();
1036            doc.incr_ignore_destructive_writes_counter();
1037            Some(doc)
1038        } else {
1039            None
1040        };
1041
1042        // Step 6.
1043        let document = self.owner_document();
1044        let old_script = document.GetCurrentScript();
1045        let introduction_type =
1046            self.introduction_type_override
1047                .get()
1048                .unwrap_or(if script.external {
1049                    IntroductionType::SRC_SCRIPT
1050                } else {
1051                    IntroductionType::INLINE_SCRIPT
1052                });
1053
1054        match script.type_ {
1055            ScriptType::Classic => {
1056                if self.upcast::<Node>().is_in_a_shadow_tree() {
1057                    document.set_current_script(None)
1058                } else {
1059                    document.set_current_script(Some(self))
1060                }
1061                self.run_a_classic_script(&script, can_gc, Some(introduction_type));
1062                document.set_current_script(old_script.as_deref());
1063            },
1064            ScriptType::Module => {
1065                document.set_current_script(None);
1066                self.run_a_module_script(&script, false, can_gc);
1067            },
1068            ScriptType::ImportMap => {
1069                // Step 6.1 Register an import map given el's relevant global object and el's result.
1070                register_import_map(&self.owner_global(), script.import_map, can_gc);
1071            },
1072        }
1073
1074        // Step 7.
1075        // Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step.
1076        if let Some(doc) = neutralized_doc {
1077            doc.decr_ignore_destructive_writes_counter();
1078        }
1079
1080        // Step 8. If el's from an external file is true, then fire an event named load at el.
1081        if script.external {
1082            self.dispatch_load_event(can_gc);
1083        }
1084    }
1085
1086    // https://html.spec.whatwg.org/multipage/#run-a-classic-script
1087    pub(crate) fn run_a_classic_script(
1088        &self,
1089        script: &ScriptOrigin,
1090        can_gc: CanGc,
1091        introduction_type: Option<&'static CStr>,
1092    ) {
1093        // TODO use a settings object rather than this element's document/window
1094        // Step 2
1095        let document = self.owner_document();
1096        if !document.is_fully_active() || !document.is_scripting_enabled() {
1097            return;
1098        }
1099
1100        // Steps 4-10
1101        let window = self.owner_window();
1102        let line_number = if script.external {
1103            1
1104        } else {
1105            self.line_number as u32
1106        };
1107        rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue());
1108        _ = window
1109            .as_global_scope()
1110            .evaluate_script_on_global_with_result(
1111                &script.code,
1112                script.url.as_str(),
1113                rval.handle_mut(),
1114                line_number,
1115                script.fetch_options.clone(),
1116                script.url.clone(),
1117                can_gc,
1118                introduction_type,
1119            );
1120    }
1121
1122    #[allow(unsafe_code)]
1123    /// <https://html.spec.whatwg.org/multipage/#run-a-module-script>
1124    pub(crate) fn run_a_module_script(
1125        &self,
1126        script: &ScriptOrigin,
1127        _rethrow_errors: bool,
1128        can_gc: CanGc,
1129    ) {
1130        // TODO use a settings object rather than this element's document/window
1131        // Step 2
1132        let document = self.owner_document();
1133        if !document.is_fully_active() || !document.is_scripting_enabled() {
1134            return;
1135        }
1136
1137        // Step 4
1138        let window = self.owner_window();
1139        let global = window.as_global_scope();
1140        let _aes = AutoEntryScript::new(global);
1141
1142        let tree = if script.external {
1143            global.get_module_map().borrow().get(&script.url).cloned()
1144        } else {
1145            global
1146                .get_inline_module_map()
1147                .borrow()
1148                .get(&self.id.clone())
1149                .cloned()
1150        };
1151
1152        if let Some(module_tree) = tree {
1153            // Step 6.
1154            {
1155                let module_error = module_tree.get_rethrow_error().borrow();
1156                let network_error = module_tree.get_network_error().borrow();
1157                if module_error.is_some() && network_error.is_none() {
1158                    module_tree.report_error(global, can_gc);
1159                    return;
1160                }
1161            }
1162
1163            let record = module_tree
1164                .get_record()
1165                .borrow()
1166                .as_ref()
1167                .map(|record| record.handle());
1168
1169            if let Some(record) = record {
1170                rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue());
1171                let evaluated =
1172                    module_tree.execute_module(global, record, rval.handle_mut().into(), can_gc);
1173
1174                if let Err(exception) = evaluated {
1175                    module_tree.set_rethrow_error(exception);
1176                    module_tree.report_error(global, can_gc);
1177                }
1178            }
1179        }
1180    }
1181
1182    pub(crate) fn queue_error_event(&self) {
1183        self.owner_global()
1184            .task_manager()
1185            .dom_manipulation_task_source()
1186            .queue_simple_event(self.upcast(), atom!("error"));
1187    }
1188
1189    pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) {
1190        self.dispatch_event(
1191            atom!("load"),
1192            EventBubbles::DoesNotBubble,
1193            EventCancelable::NotCancelable,
1194            can_gc,
1195        );
1196    }
1197
1198    pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) {
1199        self.dispatch_event(
1200            atom!("error"),
1201            EventBubbles::DoesNotBubble,
1202            EventCancelable::NotCancelable,
1203            can_gc,
1204        );
1205    }
1206
1207    // https://html.spec.whatwg.org/multipage/#prepare-a-script Step 7.
1208    pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1209        let element = self.upcast::<Element>();
1210
1211        let type_attr = element.get_attribute(&ns!(), &local_name!("type"));
1212        let language_attr = element.get_attribute(&ns!(), &local_name!("language"));
1213
1214        match (
1215            type_attr.as_ref().map(|t| t.value()),
1216            language_attr.as_ref().map(|l| l.value()),
1217        ) {
1218            (Some(ref ty), _) if ty.is_empty() => {
1219                debug!("script type empty, inferring js");
1220                Some(ScriptType::Classic)
1221            },
1222            (None, Some(ref lang)) if lang.is_empty() => {
1223                debug!("script type empty, inferring js");
1224                Some(ScriptType::Classic)
1225            },
1226            (None, None) => {
1227                debug!("script type empty, inferring js");
1228                Some(ScriptType::Classic)
1229            },
1230            (None, Some(ref lang)) => {
1231                debug!("script language={}", &***lang);
1232                let language = format!("text/{}", &***lang);
1233
1234                if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1235                    Some(ScriptType::Classic)
1236                } else {
1237                    None
1238                }
1239            },
1240            (Some(ref ty), _) => {
1241                debug!("script type={}", &***ty);
1242
1243                if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1244                    return Some(ScriptType::Module);
1245                }
1246
1247                if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1248                    return Some(ScriptType::ImportMap);
1249                }
1250
1251                if SCRIPT_JS_MIMES
1252                    .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1253                {
1254                    Some(ScriptType::Classic)
1255                } else {
1256                    None
1257                }
1258            },
1259        }
1260    }
1261
1262    pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1263        self.parser_inserted.set(parser_inserted);
1264    }
1265
1266    pub(crate) fn get_parser_inserted(&self) -> bool {
1267        self.parser_inserted.get()
1268    }
1269
1270    pub(crate) fn set_already_started(&self, already_started: bool) {
1271        self.already_started.set(already_started);
1272    }
1273
1274    pub(crate) fn get_non_blocking(&self) -> bool {
1275        self.non_blocking.get()
1276    }
1277
1278    fn dispatch_event(
1279        &self,
1280        type_: Atom,
1281        bubbles: EventBubbles,
1282        cancelable: EventCancelable,
1283        can_gc: CanGc,
1284    ) -> bool {
1285        let window = self.owner_window();
1286        let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc);
1287        event.fire(self.upcast(), can_gc)
1288    }
1289
1290    fn text(&self) -> DOMString {
1291        match self.Text() {
1292            TrustedScriptOrString::String(value) => value,
1293            TrustedScriptOrString::TrustedScript(trusted_script) => {
1294                DOMString::from(trusted_script.to_string())
1295            },
1296        }
1297    }
1298}
1299
1300impl VirtualMethods for HTMLScriptElement {
1301    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1302        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1303    }
1304
1305    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1306        self.super_type()
1307            .unwrap()
1308            .attribute_mutated(attr, mutation, can_gc);
1309        if *attr.local_name() == local_name!("src") {
1310            if let AttributeMutation::Set(_) = mutation {
1311                if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1312                    self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1313                }
1314            }
1315        }
1316    }
1317
1318    /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-26>
1319    fn children_changed(&self, mutation: &ChildrenMutation) {
1320        if let Some(s) = self.super_type() {
1321            s.children_changed(mutation);
1322        }
1323
1324        if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1325            let script = DomRoot::from_ref(self);
1326            // This method can be invoked while there are script/layout blockers present
1327            // as DOM mutations have not yet settled. We use a delayed task to avoid
1328            // running any scripts until the DOM tree is safe for interactions.
1329            self.owner_document().add_delayed_task(
1330                task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| {
1331                    script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1332                }),
1333            );
1334        }
1335    }
1336
1337    /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-20>
1338    fn post_connection_steps(&self) {
1339        if let Some(s) = self.super_type() {
1340            s.post_connection_steps();
1341        }
1342
1343        if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1344            self.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1345        }
1346    }
1347
1348    fn cloning_steps(
1349        &self,
1350        copy: &Node,
1351        maybe_doc: Option<&Document>,
1352        clone_children: CloneChildrenFlag,
1353        can_gc: CanGc,
1354    ) {
1355        if let Some(s) = self.super_type() {
1356            s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
1357        }
1358
1359        // https://html.spec.whatwg.org/multipage/#already-started
1360        if self.already_started.get() {
1361            copy.downcast::<HTMLScriptElement>()
1362                .unwrap()
1363                .set_already_started(true);
1364        }
1365    }
1366}
1367
1368impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1369    // https://html.spec.whatwg.org/multipage/#dom-script-src
1370    fn Src(&self) -> TrustedScriptURLOrUSVString {
1371        let element = self.upcast::<Element>();
1372        element.get_trusted_type_url_attribute(&local_name!("src"))
1373    }
1374
1375    /// <https://w3c.github.io/trusted-types/dist/spec/#the-src-idl-attribute>
1376    fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> {
1377        let element = self.upcast::<Element>();
1378        let local_name = &local_name!("src");
1379        let value = TrustedScriptURL::get_trusted_script_url_compliant_string(
1380            &element.owner_global(),
1381            value,
1382            "HTMLScriptElement",
1383            local_name,
1384            can_gc,
1385        )?;
1386        element.set_attribute(
1387            local_name,
1388            AttrValue::String(value.as_ref().to_owned()),
1389            can_gc,
1390        );
1391        Ok(())
1392    }
1393
1394    // https://html.spec.whatwg.org/multipage/#dom-script-type
1395    make_getter!(Type, "type");
1396    // https://html.spec.whatwg.org/multipage/#dom-script-type
1397    make_setter!(SetType, "type");
1398
1399    // https://html.spec.whatwg.org/multipage/#dom-script-charset
1400    make_getter!(Charset, "charset");
1401    // https://html.spec.whatwg.org/multipage/#dom-script-charset
1402    make_setter!(SetCharset, "charset");
1403
1404    // https://html.spec.whatwg.org/multipage/#dom-script-async
1405    fn Async(&self) -> bool {
1406        self.non_blocking.get() ||
1407            self.upcast::<Element>()
1408                .has_attribute(&local_name!("async"))
1409    }
1410
1411    // https://html.spec.whatwg.org/multipage/#dom-script-async
1412    fn SetAsync(&self, value: bool, can_gc: CanGc) {
1413        self.non_blocking.set(false);
1414        self.upcast::<Element>()
1415            .set_bool_attribute(&local_name!("async"), value, can_gc);
1416    }
1417
1418    // https://html.spec.whatwg.org/multipage/#dom-script-defer
1419    make_bool_getter!(Defer, "defer");
1420    // https://html.spec.whatwg.org/multipage/#dom-script-defer
1421    make_bool_setter!(SetDefer, "defer");
1422
1423    // https://html.spec.whatwg.org/multipage/#dom-script-nomodule
1424    make_bool_getter!(NoModule, "nomodule");
1425    // https://html.spec.whatwg.org/multipage/#dom-script-nomodule
1426    make_bool_setter!(SetNoModule, "nomodule");
1427
1428    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
1429    make_getter!(Integrity, "integrity");
1430    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
1431    make_setter!(SetIntegrity, "integrity");
1432
1433    // https://html.spec.whatwg.org/multipage/#dom-script-event
1434    make_getter!(Event, "event");
1435    // https://html.spec.whatwg.org/multipage/#dom-script-event
1436    make_setter!(SetEvent, "event");
1437
1438    // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
1439    make_getter!(HtmlFor, "for");
1440    // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
1441    make_setter!(SetHtmlFor, "for");
1442
1443    // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin
1444    fn GetCrossOrigin(&self) -> Option<DOMString> {
1445        reflect_cross_origin_attribute(self.upcast::<Element>())
1446    }
1447
1448    // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin
1449    fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
1450        set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
1451    }
1452
1453    // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
1454    fn ReferrerPolicy(&self) -> DOMString {
1455        reflect_referrer_policy_attribute(self.upcast::<Element>())
1456    }
1457
1458    // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
1459    make_setter!(SetReferrerPolicy, "referrerpolicy");
1460
1461    /// <https://w3c.github.io/trusted-types/dist/spec/#dom-htmlscriptelement-innertext>
1462    fn InnerText(&self) -> TrustedScriptOrString {
1463        // Step 1: Return the result of running get the text steps with this.
1464        TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1465    }
1466
1467    /// <https://w3c.github.io/trusted-types/dist/spec/#the-innerText-idl-attribute>
1468    fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1469        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1470        // this's relevant global object, the given value, HTMLScriptElement innerText, and script.
1471        let value = TrustedScript::get_trusted_script_compliant_string(
1472            &self.owner_global(),
1473            input,
1474            "HTMLScriptElement innerText",
1475            can_gc,
1476        )?;
1477        *self.script_text.borrow_mut() = value.clone();
1478        // Step 3: Run set the inner text steps with this and value.
1479        self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1480        Ok(())
1481    }
1482
1483    /// <https://html.spec.whatwg.org/multipage/#dom-script-text>
1484    fn Text(&self) -> TrustedScriptOrString {
1485        TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1486    }
1487
1488    /// <https://w3c.github.io/trusted-types/dist/spec/#the-text-idl-attribute>
1489    fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1490        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1491        // this's relevant global object, the given value, HTMLScriptElement text, and script.
1492        let value = TrustedScript::get_trusted_script_compliant_string(
1493            &self.owner_global(),
1494            value,
1495            "HTMLScriptElement text",
1496            can_gc,
1497        )?;
1498        // Step 2: Set this's script text value to the given value.
1499        *self.script_text.borrow_mut() = value.clone();
1500        // Step 3: String replace all with the given value within this.
1501        Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1502        Ok(())
1503    }
1504
1505    /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute>
1506    fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1507        // Step 1: Return the result of running get text content with this.
1508        Some(TrustedScriptOrString::String(
1509            self.upcast::<Node>().GetTextContent()?,
1510        ))
1511    }
1512
1513    /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute>
1514    fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1515        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1516        // this's relevant global object, the given value, HTMLScriptElement textContent, and script.
1517        let value = TrustedScript::get_trusted_script_compliant_string(
1518            &self.owner_global(),
1519            value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1520            "HTMLScriptElement textContent",
1521            can_gc,
1522        )?;
1523        // Step 2: Set this's script text value to value.
1524        *self.script_text.borrow_mut() = value.clone();
1525        // Step 3: Run set text content with this and value.
1526        self.upcast::<Node>()
1527            .set_text_content_for_element(Some(value), can_gc);
1528        Ok(())
1529    }
1530
1531    /// <https://html.spec.whatwg.org/multipage/#dom-script-supports>
1532    fn Supports(_window: &Window, type_: DOMString) -> bool {
1533        // The type argument has to exactly match these values,
1534        // we do not perform an ASCII case-insensitive match.
1535        matches!(type_.str(), "classic" | "module" | "importmap")
1536    }
1537}
1538
1539#[derive(Clone, Copy)]
1540enum ExternalScriptKind {
1541    Deferred,
1542    ParsingBlocking,
1543    AsapInOrder,
1544    Asap,
1545}