Skip to main content

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