Skip to main content

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