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