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