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