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