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