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