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