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};
16use js::context::JSContext;
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::str::DOMString;
44use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation};
45use crate::dom::document::Document;
46use crate::dom::element::{
47    AttributeMutation, Element, ElementCreator, cors_setting_for_element,
48    referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute,
49    set_cross_origin_attribute,
50};
51use crate::dom::event::{Event, EventBubbles, EventCancelable};
52use crate::dom::global_scope_script_execution::{ClassicScript, ErrorReporting, RethrowErrors};
53use crate::dom::globalscope::GlobalScope;
54use crate::dom::html::htmlelement::HTMLElement;
55use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits};
56use crate::dom::performance::performanceresourcetiming::InitiatorType;
57use crate::dom::trustedtypes::trustedscript::TrustedScript;
58use crate::dom::trustedtypes::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    cx: &mut js::context::JSContext,
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, CanGc::from_cx(cx))
276        },
277        ExternalScriptKind::AsapInOrder => {
278            document = elem.preparation_time_document.get().unwrap();
279            document.asap_in_order_script_loaded(elem, load, CanGc::from_cx(cx))
280        },
281        ExternalScriptKind::Deferred => {
282            document = elem.parser_document.as_rooted();
283            document.deferred_script_loaded(elem, load, CanGc::from_cx(cx));
284        },
285        ExternalScriptKind::ParsingBlocking => {
286            document = elem.parser_document.as_rooted();
287            document.pending_parsing_blocking_script_loaded(elem, load, cx);
288        },
289    }
290
291    document.finish_load(LoadType::Script(url), cx);
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        cx: &mut js::context::JSContext,
377        _: RequestId,
378        response: Result<(), NetworkError>,
379        timing: ResourceFetchTiming,
380    ) {
381        match (response.as_ref(), self.status.as_ref()) {
382            (Err(error), _) | (_, Err(error)) => {
383                error!("Fetching classic script failed {:?}", error);
384                // Step 6, response is an error.
385                finish_fetching_a_classic_script(
386                    &self.elem.root(),
387                    self.kind,
388                    self.url.clone(),
389                    Err(()),
390                    cx,
391                );
392
393                // Resource timing is expected to be available before "error" or "load" events are fired.
394                network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
395                return;
396            },
397            _ => {},
398        };
399
400        let metadata = self.metadata.take().unwrap();
401        let final_url = metadata.final_url;
402
403        // Step 5.3. Let potentialMIMETypeForEncoding be the result of extracting a MIME type given response's header list.
404        // Step 5.4. Set encoding to the result of legacy extracting an encoding given potentialMIMETypeForEncoding and encoding.
405        let encoding = metadata
406            .charset
407            .and_then(|encoding| Encoding::for_label(encoding.as_bytes()))
408            .unwrap_or(self.character_encoding);
409
410        // Step 5.5. Let sourceText be the result of decoding bodyBytes to Unicode, using encoding as the fallback encoding.
411        let (mut source_text, _, _) = encoding.decode(&self.data);
412
413        let elem = self.elem.root();
414        let global = elem.global();
415
416        if let Some(window) = global.downcast::<Window>() {
417            substitute_with_local_script(window, &mut source_text, final_url.clone());
418        }
419
420        // Step 5.6. Let mutedErrors be true if response was CORS-cross-origin, and false otherwise.
421        let muted_errors = self.response_was_cors_cross_origin;
422
423        // Step 5.7. Let script be the result of creating a classic script given
424        // sourceText, settingsObject, response's URL, options, mutedErrors, and url.
425        let script = global.create_a_classic_script(
426            source_text,
427            final_url.clone(),
428            self.fetch_options.clone(),
429            ErrorReporting::from(muted_errors),
430            Some(IntroductionType::SRC_SCRIPT),
431            1,
432            true,
433        );
434
435        /*
436        let options = unsafe { CompileOptionsWrapper::new(*cx, final_url.as_str(), 1) };
437
438        let can_compile_off_thread = pref!(dom_script_asynch) &&
439            unsafe { CanCompileOffThread(*cx, options.ptr as *const _, source_text.len()) };
440
441        if can_compile_off_thread {
442            let source_string = source_text.to_string();
443
444            let context = Box::new(OffThreadCompilationContext {
445                script_element: self.elem.clone(),
446                script_kind: self.kind,
447                final_url,
448                url: self.url.clone(),
449                task_source: elem.owner_global().task_manager().dom_manipulation_task_source(),
450                script_text: source_string,
451                fetch_options: self.fetch_options.clone(),
452            });
453
454            unsafe {
455                assert!(!CompileToStencilOffThread1(
456                    *cx,
457                    options.ptr as *const _,
458                    &mut transform_str_to_source_text(&context.script_text) as *mut _,
459                    Some(off_thread_compilation_callback),
460                    Box::into_raw(context) as *mut c_void,
461                )
462                .is_null());
463            }
464        } else {*/
465        let load = Script::Classic(script);
466        finish_fetching_a_classic_script(&elem, self.kind, self.url.clone(), Ok(load), cx);
467        // }
468
469        network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
470    }
471
472    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
473        let global = &self.resource_timing_global();
474        let elem = self.elem.root();
475        global.report_csp_violations(violations, Some(elem.upcast()), None);
476    }
477}
478
479impl ResourceTimingListener for ClassicContext {
480    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
481        let initiator_type = InitiatorType::LocalName(
482            self.elem
483                .root()
484                .upcast::<Element>()
485                .local_name()
486                .to_string(),
487        );
488        (initiator_type, self.url.clone())
489    }
490
491    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
492        self.elem.root().owner_document().global()
493    }
494}
495
496/// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
497// This function is also used to prefetch a script in `script::dom::servoparser::prefetch`.
498#[allow(clippy::too_many_arguments)]
499pub(crate) fn script_fetch_request(
500    webview_id: WebViewId,
501    url: ServoUrl,
502    cors_setting: Option<CorsSettings>,
503    options: ScriptFetchOptions,
504) -> RequestBuilder {
505    // We intentionally ignore options' credentials_mode member for classic scripts.
506    // The mode is initialized by create_a_potential_cors_request.
507    create_a_potential_cors_request(
508        Some(webview_id),
509        url,
510        Destination::Script,
511        cors_setting,
512        None,
513        options.referrer,
514    )
515    .parser_metadata(options.parser_metadata)
516    .integrity_metadata(options.integrity_metadata.clone())
517    .referrer_policy(options.referrer_policy)
518    .cryptographic_nonce_metadata(options.cryptographic_nonce)
519}
520
521/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script>
522fn fetch_a_classic_script(
523    script: &HTMLScriptElement,
524    kind: ExternalScriptKind,
525    url: ServoUrl,
526    cors_setting: Option<CorsSettings>,
527    options: ScriptFetchOptions,
528    character_encoding: &'static Encoding,
529) {
530    // Step 1, 2.
531    let doc = script.owner_document();
532    let global = script.global();
533    let request =
534        script_fetch_request(doc.webview_id(), url.clone(), cors_setting, options.clone())
535            .with_global_scope(&global);
536
537    // TODO: Step 3, Add custom steps to perform fetch
538
539    let context = ClassicContext {
540        elem: Trusted::new(script),
541        kind,
542        character_encoding,
543        data: vec![],
544        metadata: None,
545        url: url.clone(),
546        status: Ok(()),
547        fetch_options: options,
548        response_was_cors_cross_origin: false,
549    };
550    doc.fetch(LoadType::Script(url), request, context);
551}
552
553impl HTMLScriptElement {
554    /// <https://w3c.github.io/trusted-types/dist/spec/#setting-slot-values-from-parser>
555    pub(crate) fn set_initial_script_text(&self) {
556        *self.script_text.borrow_mut() = self.text();
557    }
558
559    /// <https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-prepare-the-script-text>
560    fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> {
561        // Step 1. If script’s script text value is not equal to its child text content,
562        // set script’s script text to the result of executing
563        // Get Trusted Type compliant string, with the following arguments:
564        if self.script_text.borrow().clone() != self.text() {
565            *self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string(
566                &self.owner_global(),
567                self.Text(),
568                "HTMLScriptElement text",
569                can_gc,
570            )?;
571        }
572
573        Ok(())
574    }
575
576    /// <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
577    pub(crate) fn prepare(&self, introduction_type_override: Option<&'static CStr>, can_gc: CanGc) {
578        self.introduction_type_override
579            .set(introduction_type_override);
580
581        // Step 1. If el's already started is true, then return.
582        if self.already_started.get() {
583            return;
584        }
585
586        // Step 2. Let parser document be el's parser document.
587        // TODO
588
589        // Step 3. Set el's parser document to null.
590        let was_parser_inserted = self.parser_inserted.get();
591        self.parser_inserted.set(false);
592
593        // Step 4.
594        // If parser document is non-null and el does not have an async attribute, then set el's force async to true.
595        let element = self.upcast::<Element>();
596        let asynch = element.has_attribute(&local_name!("async"));
597        // Note: confusingly, this is done if the element does *not* have an "async" attribute.
598        if was_parser_inserted && !asynch {
599            self.non_blocking.set(true);
600        }
601
602        // Step 5. Execute the Prepare the script text algorithm on el.
603        // If that algorithm threw an error, then return.
604        if self.prepare_the_script_text(can_gc).is_err() {
605            return;
606        }
607        // Step 5a. Let source text be el’s script text value.
608        let text = self.script_text.borrow().clone();
609        // Step 6. If el has no src attribute, and source text is the empty string, then return.
610        if text.is_empty() && !element.has_attribute(&local_name!("src")) {
611            return;
612        }
613
614        // Step 7. If el is not connected, then return.
615        if !self.upcast::<Node>().is_connected() {
616            return;
617        }
618
619        let script_type = if let Some(ty) = self.get_script_type() {
620            // Step 9-11.
621            ty
622        } else {
623            // Step 12. Otherwise, return. (No script is executed, and el's type is left as null.)
624            return;
625        };
626
627        // Step 13.
628        // If parser document is non-null, then set el's parser document back to parser document and set el's force
629        // async to false.
630        if was_parser_inserted {
631            self.parser_inserted.set(true);
632            self.non_blocking.set(false);
633        }
634
635        // Step 14. Set el's already started to true.
636        self.already_started.set(true);
637
638        // Step 15. Set el's preparation-time document to its node document.
639        let doc = self.owner_document();
640        self.preparation_time_document.set(Some(&doc));
641
642        // Step 16.
643        // If parser document is non-null, and parser document is not equal to el's preparation-time document, then
644        // return.
645        if self.parser_inserted.get() && *self.parser_document != *doc {
646            return;
647        }
648
649        // Step 17. If scripting is disabled for el, then return.
650        if !doc.scripting_enabled() {
651            return;
652        }
653
654        // Step 18. If el has a nomodule content attribute and its type is "classic", then return.
655        if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic {
656            return;
657        }
658
659        let global = &doc.global();
660
661        // Step 19. CSP.
662        if !element.has_attribute(&local_name!("src")) &&
663            global
664                .get_csp_list()
665                .should_elements_inline_type_behavior_be_blocked(
666                    global,
667                    element,
668                    InlineCheckType::Script,
669                    &text.str(),
670                    self.line_number as u32,
671                )
672        {
673            warn!("Blocking inline script due to CSP");
674            return;
675        }
676
677        // Step 20. If el has an event attribute and a for attribute, and el's type is "classic", then:
678        if script_type == ScriptType::Classic {
679            let for_attribute = element.get_attribute(&local_name!("for"));
680            let event_attribute = element.get_attribute(&local_name!("event"));
681            if let (Some(ref for_attribute), Some(ref event_attribute)) =
682                (for_attribute, event_attribute)
683            {
684                let for_value = for_attribute.value().to_ascii_lowercase();
685                let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS);
686                if for_value != "window" {
687                    return;
688                }
689
690                let event_value = event_attribute.value().to_ascii_lowercase();
691                let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS);
692                if event_value != "onload" && event_value != "onload()" {
693                    return;
694                }
695            }
696        }
697
698        // Step 21. If el has a charset attribute, then let encoding be the result of getting
699        // an encoding from the value of the charset attribute.
700        // If el does not have a charset attribute, or if getting an encoding failed,
701        // then let encoding be el's node document's the encoding.
702        let encoding = element
703            .get_attribute(&local_name!("charset"))
704            .and_then(|charset| Encoding::for_label(charset.value().as_bytes()))
705            .unwrap_or_else(|| doc.encoding());
706
707        // Step 22. CORS setting.
708        let cors_setting = cors_setting_for_element(element);
709
710        // Step 23. Module script credentials mode.
711        let module_credentials_mode = match script_type {
712            ScriptType::Classic => CredentialsMode::CredentialsSameOrigin,
713            ScriptType::Module | ScriptType::ImportMap => reflect_cross_origin_attribute(element)
714                .map_or(
715                    CredentialsMode::CredentialsSameOrigin,
716                    |attr| match &*attr.str() {
717                        "use-credentials" => CredentialsMode::Include,
718                        "anonymous" => CredentialsMode::CredentialsSameOrigin,
719                        _ => CredentialsMode::CredentialsSameOrigin,
720                    },
721                ),
722        };
723
724        // Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
725        // If the element has a nonce content attribute but is not nonceable strip the nonce to prevent injection attacks.
726        // Elements without a nonce content attribute (e.g. JS-created with .nonce = "abc")
727        // use the internal slot directly — the nonceable check only applies to parser-created elements.
728        let el = self.upcast::<Element>();
729        let cryptographic_nonce = if el.is_nonceable() || !el.has_attribute(&local_name!("nonce")) {
730            el.nonce_value().trim().to_owned()
731        } else {
732            String::new()
733        };
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(&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 mut 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(&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                    // If el does not have an integrity attribute, then set options's integrity metadata to
845                    // the result of resolving a module integrity metadata with url and settings object.
846                    if integrity_val.is_none() {
847                        options.integrity_metadata = global
848                            .import_map()
849                            .resolve_a_module_integrity_metadata(&url);
850                    }
851
852                    // Step 31.11. Fetch an external module script graph.
853                    fetch_an_external_module_script(
854                        url.clone(),
855                        ModuleOwner::Window(Trusted::new(self)),
856                        options,
857                        can_gc,
858                    );
859
860                    if !asynch && was_parser_inserted {
861                        // 33.4: module, not async, parser-inserted
862                        doc.add_deferred_script(self);
863                    } else if !asynch && !self.non_blocking.get() {
864                        // 33.3: module, not parser-inserted
865                        doc.push_asap_in_order_script(self);
866                    } else {
867                        // 33.2: module, async
868                        doc.add_asap_script(self);
869                    };
870                },
871                ScriptType::ImportMap => (),
872            }
873        } else {
874            // Step 32. If el does not have a src content attribute:
875
876            assert!(!text.is_empty());
877
878            let text_rc = Rc::new(text.clone());
879
880            // Step 32.2: Switch on el's type:
881            match script_type {
882                ScriptType::Classic => {
883                    // Step 32.2.1 Let script be the result of creating a classic script
884                    // using source text, settings object, base URL, and options.
885                    let script = self.global().create_a_classic_script(
886                        std::borrow::Cow::Borrowed(&text.str()),
887                        base_url,
888                        options,
889                        ErrorReporting::Unmuted,
890                        introduction_type_override.or(Some(IntroductionType::INLINE_SCRIPT)),
891                        self.line_number as u32,
892                        false,
893                    );
894                    let result = Ok(Script::Classic(script));
895
896                    if was_parser_inserted &&
897                        doc.get_current_parser()
898                            .is_some_and(|parser| parser.script_nesting_level() <= 1) &&
899                        doc.get_script_blocking_stylesheets_count() > 0
900                    {
901                        // Step 34.2: classic, has no src, was parser-inserted, is blocked on stylesheet.
902                        doc.set_pending_parsing_blocking_script(self, Some(result));
903                    } else {
904                        // Step 34.3: otherwise.
905                        self.execute(result, can_gc);
906                    }
907                },
908                ScriptType::Module => {
909                    // We should add inline module script elements
910                    // into those vectors in case that there's no
911                    // descendants in the inline module script.
912                    if !asynch && was_parser_inserted {
913                        doc.add_deferred_script(self);
914                    } else if !asynch && !self.non_blocking.get() {
915                        doc.push_asap_in_order_script(self);
916                    } else {
917                        doc.add_asap_script(self);
918                    };
919
920                    fetch_inline_module_script(
921                        ModuleOwner::Window(Trusted::new(self)),
922                        text_rc,
923                        base_url.clone(),
924                        options,
925                        self.line_number as u32,
926                        can_gc,
927                    );
928                },
929                ScriptType::ImportMap => {
930                    // Step 32.1 Let result be the result of creating an import map
931                    // parse result given source text and base URL.
932                    let import_map_result = parse_an_import_map_string(
933                        ModuleOwner::Window(Trusted::new(self)),
934                        Rc::clone(&text_rc),
935                        base_url.clone(),
936                    );
937                    let script = Script::ImportMap(ScriptOrigin::internal(
938                        text_rc,
939                        base_url,
940                        options,
941                        script_type,
942                        self.global().unminified_js_dir(),
943                        import_map_result,
944                    ));
945
946                    // Step 34.3
947                    self.execute(Ok(script), can_gc);
948                },
949            }
950        }
951    }
952
953    /// <https://html.spec.whatwg.org/multipage/#execute-the-script-element>
954    pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) {
955        // Step 1. Let document be el's node document.
956        let doc = self.owner_document();
957
958        // Step 2. If el's preparation-time document is not equal to document, then return.
959        if *doc != *self.preparation_time_document.get().unwrap() {
960            return;
961        }
962
963        // TODO: Step 3. Unblock rendering on el.
964        let script = match result {
965            // Step 4. If el's result is null, then fire an event named error at el, and return.
966            Err(_) => {
967                self.dispatch_error_event(can_gc);
968                return;
969            },
970
971            Ok(script) => script,
972        };
973
974        // Step 5.
975        // If el's from an external file is true, or el's type is "module", then increment document's
976        // ignore-destructive-writes counter.
977        let neutralized_doc =
978            if self.from_an_external_file.get() || matches!(script, Script::Module(_)) {
979                let doc = self.owner_document();
980                doc.incr_ignore_destructive_writes_counter();
981                Some(doc)
982            } else {
983                None
984            };
985
986        let document = self.owner_document();
987
988        match script {
989            Script::Classic(script) => {
990                // Step 6."classic".1. Let oldCurrentScript be the value to which document's currentScript object was most recently set.
991                let old_script = document.GetCurrentScript();
992
993                // Step 6."classic".2. If el's root is not a shadow root,
994                // then set document's currentScript attribute to el. Otherwise, set it to null.
995                if self.upcast::<Node>().is_in_a_shadow_tree() {
996                    document.set_current_script(None)
997                } else {
998                    document.set_current_script(Some(self))
999                }
1000
1001                // Step 6."classic".3. Run the classic script given by el's result.
1002                _ = self.owner_window().as_global_scope().run_a_classic_script(
1003                    script,
1004                    RethrowErrors::No,
1005                    can_gc,
1006                );
1007
1008                // Step 6."classic".4. Set document's currentScript attribute to oldCurrentScript.
1009                document.set_current_script(old_script.as_deref());
1010            },
1011            Script::Module(module_tree) => {
1012                // TODO Step 6."module".1. Assert: document's currentScript attribute is null.
1013                document.set_current_script(None);
1014
1015                // Step 6."module".2. Run the module script given by el's result.
1016                self.owner_window().as_global_scope().run_a_module_script(
1017                    module_tree,
1018                    false,
1019                    can_gc,
1020                );
1021            },
1022            Script::ImportMap(script) => {
1023                // Step 6."importmap".1. Register an import map given el's relevant global object and el's result.
1024                register_import_map(&self.owner_global(), script.import_map, can_gc);
1025            },
1026        }
1027
1028        // Step 7.
1029        // Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step.
1030        if let Some(doc) = neutralized_doc {
1031            doc.decr_ignore_destructive_writes_counter();
1032        }
1033
1034        // Step 8. If el's from an external file is true, then fire an event named load at el.
1035        if self.from_an_external_file.get() {
1036            self.dispatch_load_event(can_gc);
1037        }
1038    }
1039
1040    pub(crate) fn queue_error_event(&self) {
1041        self.owner_global()
1042            .task_manager()
1043            .dom_manipulation_task_source()
1044            .queue_simple_event(self.upcast(), atom!("error"));
1045    }
1046
1047    pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) {
1048        self.dispatch_event(
1049            atom!("load"),
1050            EventBubbles::DoesNotBubble,
1051            EventCancelable::NotCancelable,
1052            can_gc,
1053        );
1054    }
1055
1056    pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) {
1057        self.dispatch_event(
1058            atom!("error"),
1059            EventBubbles::DoesNotBubble,
1060            EventCancelable::NotCancelable,
1061            can_gc,
1062        );
1063    }
1064
1065    // https://html.spec.whatwg.org/multipage/#prepare-a-script Step 7.
1066    pub(crate) fn get_script_type(&self) -> Option<ScriptType> {
1067        let element = self.upcast::<Element>();
1068
1069        let type_attr = element.get_attribute(&local_name!("type"));
1070        let language_attr = element.get_attribute(&local_name!("language"));
1071
1072        match (
1073            type_attr.as_ref().map(|t| t.value()),
1074            language_attr.as_ref().map(|l| l.value()),
1075        ) {
1076            (Some(ref ty), _) if ty.is_empty() => {
1077                debug!("script type empty, inferring js");
1078                Some(ScriptType::Classic)
1079            },
1080            (None, Some(ref lang)) if lang.is_empty() => {
1081                debug!("script type empty, inferring js");
1082                Some(ScriptType::Classic)
1083            },
1084            (None, None) => {
1085                debug!("script type empty, inferring js");
1086                Some(ScriptType::Classic)
1087            },
1088            (None, Some(ref lang)) => {
1089                debug!("script language={}", &***lang);
1090                let language = format!("text/{}", &***lang);
1091
1092                if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) {
1093                    Some(ScriptType::Classic)
1094                } else {
1095                    None
1096                }
1097            },
1098            (Some(ref ty), _) => {
1099                debug!("script type={}", &***ty);
1100
1101                if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" {
1102                    return Some(ScriptType::Module);
1103                }
1104
1105                if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" {
1106                    return Some(ScriptType::ImportMap);
1107                }
1108
1109                if SCRIPT_JS_MIMES
1110                    .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
1111                {
1112                    Some(ScriptType::Classic)
1113                } else {
1114                    None
1115                }
1116            },
1117        }
1118    }
1119
1120    pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) {
1121        self.parser_inserted.set(parser_inserted);
1122    }
1123
1124    pub(crate) fn get_parser_inserted(&self) -> bool {
1125        self.parser_inserted.get()
1126    }
1127
1128    pub(crate) fn set_already_started(&self, already_started: bool) {
1129        self.already_started.set(already_started);
1130    }
1131
1132    pub(crate) fn get_non_blocking(&self) -> bool {
1133        self.non_blocking.get()
1134    }
1135
1136    fn dispatch_event(
1137        &self,
1138        type_: Atom,
1139        bubbles: EventBubbles,
1140        cancelable: EventCancelable,
1141        can_gc: CanGc,
1142    ) -> bool {
1143        let window = self.owner_window();
1144        let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc);
1145        event.fire(self.upcast(), can_gc)
1146    }
1147
1148    fn text(&self) -> DOMString {
1149        match self.Text() {
1150            TrustedScriptOrString::String(value) => value,
1151            TrustedScriptOrString::TrustedScript(trusted_script) => {
1152                DOMString::from(trusted_script.to_string())
1153            },
1154        }
1155    }
1156}
1157
1158impl VirtualMethods for HTMLScriptElement {
1159    fn super_type(&self) -> Option<&dyn VirtualMethods> {
1160        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1161    }
1162
1163    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
1164        self.super_type()
1165            .unwrap()
1166            .attribute_mutated(attr, mutation, can_gc);
1167        if *attr.local_name() == local_name!("src") {
1168            if let AttributeMutation::Set(..) = mutation {
1169                if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() {
1170                    self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc);
1171                }
1172            }
1173        }
1174    }
1175
1176    /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-26>
1177    fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
1178        if let Some(s) = self.super_type() {
1179            s.children_changed(mutation, can_gc);
1180        }
1181
1182        if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1183            let script = DomRoot::from_ref(self);
1184            // This method can be invoked while there are script/layout blockers present
1185            // as DOM mutations have not yet settled. We use a delayed task to avoid
1186            // running any scripts until the DOM tree is safe for interactions.
1187            self.owner_document().add_delayed_task(
1188                task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| {
1189                    script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note());
1190                }),
1191            );
1192        }
1193    }
1194
1195    /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-20>
1196    fn post_connection_steps(&self, cx: &mut JSContext) {
1197        if let Some(s) = self.super_type() {
1198            s.post_connection_steps(cx);
1199        }
1200
1201        if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() {
1202            self.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::from_cx(cx));
1203        }
1204    }
1205
1206    fn cloning_steps(
1207        &self,
1208        cx: &mut JSContext,
1209        copy: &Node,
1210        maybe_doc: Option<&Document>,
1211        clone_children: CloneChildrenFlag,
1212    ) {
1213        if let Some(s) = self.super_type() {
1214            s.cloning_steps(cx, copy, maybe_doc, clone_children);
1215        }
1216
1217        // https://html.spec.whatwg.org/multipage/#already-started
1218        if self.already_started.get() {
1219            copy.downcast::<HTMLScriptElement>()
1220                .unwrap()
1221                .set_already_started(true);
1222        }
1223    }
1224}
1225
1226impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
1227    /// <https://html.spec.whatwg.org/multipage/#dom-script-src>
1228    fn Src(&self) -> TrustedScriptURLOrUSVString {
1229        let element = self.upcast::<Element>();
1230        element.get_trusted_type_url_attribute(&local_name!("src"))
1231    }
1232
1233    /// <https://w3c.github.io/trusted-types/dist/spec/#the-src-idl-attribute>
1234    fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> {
1235        let element = self.upcast::<Element>();
1236        let local_name = &local_name!("src");
1237        let value = TrustedScriptURL::get_trusted_script_url_compliant_string(
1238            &element.owner_global(),
1239            value,
1240            "HTMLScriptElement",
1241            local_name,
1242            can_gc,
1243        )?;
1244        element.set_attribute(
1245            local_name,
1246            AttrValue::String(value.str().to_owned()),
1247            can_gc,
1248        );
1249        Ok(())
1250    }
1251
1252    // https://html.spec.whatwg.org/multipage/#dom-script-type
1253    make_getter!(Type, "type");
1254    // https://html.spec.whatwg.org/multipage/#dom-script-type
1255    make_setter!(SetType, "type");
1256
1257    // https://html.spec.whatwg.org/multipage/#dom-script-charset
1258    make_getter!(Charset, "charset");
1259    // https://html.spec.whatwg.org/multipage/#dom-script-charset
1260    make_setter!(SetCharset, "charset");
1261
1262    /// <https://html.spec.whatwg.org/multipage/#dom-script-async>
1263    fn Async(&self) -> bool {
1264        self.non_blocking.get() ||
1265            self.upcast::<Element>()
1266                .has_attribute(&local_name!("async"))
1267    }
1268
1269    /// <https://html.spec.whatwg.org/multipage/#dom-script-async>
1270    fn SetAsync(&self, value: bool, can_gc: CanGc) {
1271        self.non_blocking.set(false);
1272        self.upcast::<Element>()
1273            .set_bool_attribute(&local_name!("async"), value, can_gc);
1274    }
1275
1276    // https://html.spec.whatwg.org/multipage/#dom-script-defer
1277    make_bool_getter!(Defer, "defer");
1278    // https://html.spec.whatwg.org/multipage/#dom-script-defer
1279    make_bool_setter!(SetDefer, "defer");
1280
1281    // https://html.spec.whatwg.org/multipage/#dom-script-nomodule
1282    make_bool_getter!(NoModule, "nomodule");
1283    // https://html.spec.whatwg.org/multipage/#dom-script-nomodule
1284    make_bool_setter!(SetNoModule, "nomodule");
1285
1286    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
1287    make_getter!(Integrity, "integrity");
1288    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
1289    make_setter!(SetIntegrity, "integrity");
1290
1291    // https://html.spec.whatwg.org/multipage/#dom-script-event
1292    make_getter!(Event, "event");
1293    // https://html.spec.whatwg.org/multipage/#dom-script-event
1294    make_setter!(SetEvent, "event");
1295
1296    // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
1297    make_getter!(HtmlFor, "for");
1298    // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
1299    make_setter!(SetHtmlFor, "for");
1300
1301    /// <https://html.spec.whatwg.org/multipage/#dom-script-crossorigin>
1302    fn GetCrossOrigin(&self) -> Option<DOMString> {
1303        reflect_cross_origin_attribute(self.upcast::<Element>())
1304    }
1305
1306    /// <https://html.spec.whatwg.org/multipage/#dom-script-crossorigin>
1307    fn SetCrossOrigin(&self, cx: &mut JSContext, value: Option<DOMString>) {
1308        set_cross_origin_attribute(cx, self.upcast::<Element>(), value);
1309    }
1310
1311    /// <https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy>
1312    fn ReferrerPolicy(&self) -> DOMString {
1313        reflect_referrer_policy_attribute(self.upcast::<Element>())
1314    }
1315
1316    // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy
1317    make_setter!(SetReferrerPolicy, "referrerpolicy");
1318
1319    /// <https://w3c.github.io/trusted-types/dist/spec/#dom-htmlscriptelement-innertext>
1320    fn InnerText(&self) -> TrustedScriptOrString {
1321        // Step 1: Return the result of running get the text steps with this.
1322        TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text())
1323    }
1324
1325    /// <https://w3c.github.io/trusted-types/dist/spec/#the-innerText-idl-attribute>
1326    fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1327        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1328        // this's relevant global object, the given value, HTMLScriptElement innerText, and script.
1329        let value = TrustedScript::get_trusted_script_compliant_string(
1330            &self.owner_global(),
1331            input,
1332            "HTMLScriptElement innerText",
1333            can_gc,
1334        )?;
1335        *self.script_text.borrow_mut() = value.clone();
1336        // Step 3: Run set the inner text steps with this and value.
1337        self.upcast::<HTMLElement>().set_inner_text(value, can_gc);
1338        Ok(())
1339    }
1340
1341    /// <https://html.spec.whatwg.org/multipage/#dom-script-text>
1342    fn Text(&self) -> TrustedScriptOrString {
1343        TrustedScriptOrString::String(self.upcast::<Node>().child_text_content())
1344    }
1345
1346    /// <https://w3c.github.io/trusted-types/dist/spec/#the-text-idl-attribute>
1347    fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> {
1348        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1349        // this's relevant global object, the given value, HTMLScriptElement text, and script.
1350        let value = TrustedScript::get_trusted_script_compliant_string(
1351            &self.owner_global(),
1352            value,
1353            "HTMLScriptElement text",
1354            can_gc,
1355        )?;
1356        // Step 2: Set this's script text value to the given value.
1357        *self.script_text.borrow_mut() = value.clone();
1358        // Step 3: String replace all with the given value within this.
1359        Node::string_replace_all(value, self.upcast::<Node>(), can_gc);
1360        Ok(())
1361    }
1362
1363    /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute>
1364    fn GetTextContent(&self) -> Option<TrustedScriptOrString> {
1365        // Step 1: Return the result of running get text content with this.
1366        Some(TrustedScriptOrString::String(
1367            self.upcast::<Node>().GetTextContent()?,
1368        ))
1369    }
1370
1371    /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute>
1372    fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> {
1373        // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript,
1374        // this's relevant global object, the given value, HTMLScriptElement textContent, and script.
1375        let value = TrustedScript::get_trusted_script_compliant_string(
1376            &self.owner_global(),
1377            value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))),
1378            "HTMLScriptElement textContent",
1379            can_gc,
1380        )?;
1381        // Step 2: Set this's script text value to value.
1382        *self.script_text.borrow_mut() = value.clone();
1383        // Step 3: Run set text content with this and value.
1384        self.upcast::<Node>()
1385            .set_text_content_for_element(Some(value), can_gc);
1386        Ok(())
1387    }
1388
1389    /// <https://html.spec.whatwg.org/multipage/#dom-script-supports>
1390    fn Supports(_window: &Window, type_: DOMString) -> bool {
1391        // The type argument has to exactly match these values,
1392        // we do not perform an ASCII case-insensitive match.
1393        matches!(&*type_.str(), "classic" | "module" | "importmap")
1394    }
1395}
1396
1397pub(crate) fn substitute_with_local_script(
1398    window: &Window,
1399    script: &mut Cow<'_, str>,
1400    url: ServoUrl,
1401) {
1402    if window.local_script_source().is_none() {
1403        return;
1404    }
1405    let mut path = PathBuf::from(window.local_script_source().clone().unwrap());
1406    path = path.join(&url[url::Position::BeforeHost..]);
1407    debug!("Attempting to read script stored at: {:?}", path);
1408    match read_to_string(path.clone()) {
1409        Ok(local_script) => {
1410            debug!("Found script stored at: {:?}", path);
1411            *script = Cow::Owned(local_script);
1412        },
1413        Err(why) => warn!("Could not restore script from file {:?}", why),
1414    }
1415}
1416
1417#[derive(Clone, Copy)]
1418enum ExternalScriptKind {
1419    Deferred,
1420    ParsingBlocking,
1421    AsapInOrder,
1422    Asap,
1423}