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