Skip to main content

script/dom/html/
htmlscriptelement.rs

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