| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| use std::cell::Cell; |
| use std::ffi::CStr; |
| use std::fs::read_to_string; |
| use std::path::PathBuf; |
| use std::rc::Rc; |
| |
| use base::id::{PipelineId, WebViewId}; |
| use dom_struct::dom_struct; |
| use encoding_rs::Encoding; |
| use html5ever::{LocalName, Prefix, local_name, ns}; |
| use js::jsval::UndefinedValue; |
| use js::rust::{HandleObject, Stencil}; |
| use net_traits::http_status::HttpStatus; |
| use net_traits::policy_container::PolicyContainer; |
| use net_traits::request::{ |
| CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, |
| RequestBuilder, RequestId, |
| }; |
| use net_traits::{ |
| FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming, |
| ResourceTimingType, |
| }; |
| use servo_url::{ImmutableOrigin, ServoUrl}; |
| use style::attr::AttrValue; |
| use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec}; |
| use stylo_atoms::Atom; |
| use uuid::Uuid; |
| |
| use crate::document_loader::LoadType; |
| use crate::dom::attr::Attr; |
| use crate::dom::bindings::cell::DomRefCell; |
| use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; |
| use crate::dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods; |
| use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; |
| use crate::dom::bindings::codegen::UnionTypes::{ |
| TrustedScriptOrString, TrustedScriptURLOrUSVString, |
| }; |
| use crate::dom::bindings::error::{Error, Fallible}; |
| use crate::dom::bindings::inheritance::Castable; |
| use crate::dom::bindings::refcounted::Trusted; |
| use crate::dom::bindings::reflector::DomGlobal; |
| use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; |
| use crate::dom::bindings::settings_stack::AutoEntryScript; |
| use crate::dom::bindings::str::DOMString; |
| use crate::dom::bindings::trace::NoTrace; |
| use crate::dom::csp::{CspReporting, GlobalCspReporting, InlineCheckType, Violation}; |
| use crate::dom::document::Document; |
| use crate::dom::element::{ |
| AttributeMutation, Element, ElementCreator, cors_setting_for_element, |
| referrer_policy_for_element, reflect_cross_origin_attribute, reflect_referrer_policy_attribute, |
| set_cross_origin_attribute, |
| }; |
| use crate::dom::event::{Event, EventBubbles, EventCancelable}; |
| use crate::dom::globalscope::GlobalScope; |
| use crate::dom::html::htmlelement::HTMLElement; |
| use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeTraits}; |
| use crate::dom::performanceresourcetiming::InitiatorType; |
| use crate::dom::trustedscript::TrustedScript; |
| use crate::dom::trustedscripturl::TrustedScriptURL; |
| use crate::dom::virtualmethods::VirtualMethods; |
| use crate::dom::window::Window; |
| use crate::fetch::create_a_potential_cors_request; |
| use crate::network_listener::{self, PreInvoke, ResourceTimingListener}; |
| use crate::realms::enter_realm; |
| use crate::script_module::{ |
| ImportMap, ModuleOwner, ScriptFetchOptions, fetch_external_module_script, |
| fetch_inline_module_script, parse_an_import_map_string, register_import_map, |
| }; |
| use crate::script_runtime::{CanGc, IntroductionType}; |
| use crate::unminify::{ScriptSource, unminify_js}; |
| |
| impl ScriptSource for ScriptOrigin { |
| fn unminified_dir(&self) -> Option<String> { |
| self.unminified_dir.clone() |
| } |
| |
| fn extract_bytes(&self) -> &[u8] { |
| match &self.code { |
| SourceCode::Text(text) => text.as_bytes(), |
| SourceCode::Compiled(compiled_source_code) => { |
| compiled_source_code.original_text.as_bytes() |
| }, |
| } |
| } |
| |
| fn rewrite_source(&mut self, source: Rc<DOMString>) { |
| self.code = SourceCode::Text(source); |
| } |
| |
| fn url(&self) -> ServoUrl { |
| self.url.clone() |
| } |
| |
| fn is_external(&self) -> bool { |
| self.external |
| } |
| } |
| |
| /// An unique id for script element. |
| #[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)] |
| pub(crate) struct ScriptId(#[no_trace] Uuid); |
| |
| #[dom_struct] |
| pub(crate) struct HTMLScriptElement { |
| htmlelement: HTMLElement, |
| |
| /// <https://html.spec.whatwg.org/multipage/#already-started> |
| already_started: Cell<bool>, |
| |
| /// <https://html.spec.whatwg.org/multipage/#parser-inserted> |
| parser_inserted: Cell<bool>, |
| |
| /// <https://html.spec.whatwg.org/multipage/#non-blocking> |
| /// |
| /// (currently unused) |
| non_blocking: Cell<bool>, |
| |
| /// Document of the parser that created this element |
| /// <https://html.spec.whatwg.org/multipage/#parser-document> |
| parser_document: Dom<Document>, |
| |
| /// Prevents scripts that move between documents during preparation from executing. |
| /// <https://html.spec.whatwg.org/multipage/#preparation-time-document> |
| preparation_time_document: MutNullableDom<Document>, |
| |
| /// Track line line_number |
| line_number: u64, |
| |
| /// Unique id for each script element |
| #[ignore_malloc_size_of = "Defined in uuid"] |
| id: ScriptId, |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#htmlscriptelement-script-text> |
| script_text: DomRefCell<DOMString>, |
| |
| /// `introductionType` value to set in the `CompileOptionsWrapper`, overriding the usual |
| /// `srcScript` or `inlineScript` that this script would normally use. |
| #[no_trace] |
| introduction_type_override: Cell<Option<&'static CStr>>, |
| } |
| |
| impl HTMLScriptElement { |
| fn new_inherited( |
| local_name: LocalName, |
| prefix: Option<Prefix>, |
| document: &Document, |
| creator: ElementCreator, |
| ) -> HTMLScriptElement { |
| HTMLScriptElement { |
| id: ScriptId(Uuid::new_v4()), |
| htmlelement: HTMLElement::new_inherited(local_name, prefix, document), |
| already_started: Cell::new(false), |
| parser_inserted: Cell::new(creator.is_parser_created()), |
| non_blocking: Cell::new(!creator.is_parser_created()), |
| parser_document: Dom::from_ref(document), |
| preparation_time_document: MutNullableDom::new(None), |
| line_number: creator.return_line_number(), |
| script_text: DomRefCell::new(DOMString::new()), |
| introduction_type_override: Cell::new(None), |
| } |
| } |
| |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| pub(crate) fn new( |
| local_name: LocalName, |
| prefix: Option<Prefix>, |
| document: &Document, |
| proto: Option<HandleObject>, |
| creator: ElementCreator, |
| can_gc: CanGc, |
| ) -> DomRoot<HTMLScriptElement> { |
| Node::reflect_node_with_proto( |
| Box::new(HTMLScriptElement::new_inherited( |
| local_name, prefix, document, creator, |
| )), |
| document, |
| proto, |
| can_gc, |
| ) |
| } |
| |
| pub(crate) fn get_script_id(&self) -> ScriptId { |
| self.id |
| } |
| } |
| |
| /// Supported script types as defined by |
| /// <https://html.spec.whatwg.org/multipage/#javascript-mime-type>. |
| pub(crate) static SCRIPT_JS_MIMES: StaticStringVec = &[ |
| "application/ecmascript", |
| "application/javascript", |
| "application/x-ecmascript", |
| "application/x-javascript", |
| "text/ecmascript", |
| "text/javascript", |
| "text/javascript1.0", |
| "text/javascript1.1", |
| "text/javascript1.2", |
| "text/javascript1.3", |
| "text/javascript1.4", |
| "text/javascript1.5", |
| "text/jscript", |
| "text/livescript", |
| "text/x-ecmascript", |
| "text/x-javascript", |
| ]; |
| |
| #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] |
| pub(crate) enum ScriptType { |
| Classic, |
| Module, |
| ImportMap, |
| } |
| |
| #[derive(JSTraceable, MallocSizeOf)] |
| pub(crate) struct CompiledSourceCode { |
| #[ignore_malloc_size_of = "SM handles JS values"] |
| pub(crate) source_code: Stencil, |
| #[conditional_malloc_size_of = "Rc is hard"] |
| pub(crate) original_text: Rc<DOMString>, |
| } |
| |
| #[derive(JSTraceable, MallocSizeOf)] |
| pub(crate) enum SourceCode { |
| Text(#[conditional_malloc_size_of] Rc<DOMString>), |
| Compiled(CompiledSourceCode), |
| } |
| |
| #[derive(JSTraceable, MallocSizeOf)] |
| pub(crate) struct ScriptOrigin { |
| code: SourceCode, |
| #[no_trace] |
| url: ServoUrl, |
| external: bool, |
| fetch_options: ScriptFetchOptions, |
| type_: ScriptType, |
| unminified_dir: Option<String>, |
| import_map: Fallible<ImportMap>, |
| } |
| |
| impl ScriptOrigin { |
| pub(crate) fn internal( |
| text: Rc<DOMString>, |
| url: ServoUrl, |
| fetch_options: ScriptFetchOptions, |
| type_: ScriptType, |
| unminified_dir: Option<String>, |
| import_map: Fallible<ImportMap>, |
| ) -> ScriptOrigin { |
| ScriptOrigin { |
| code: SourceCode::Text(text), |
| url, |
| external: false, |
| fetch_options, |
| type_, |
| unminified_dir, |
| import_map, |
| } |
| } |
| |
| pub(crate) fn external( |
| text: Rc<DOMString>, |
| url: ServoUrl, |
| fetch_options: ScriptFetchOptions, |
| type_: ScriptType, |
| unminified_dir: Option<String>, |
| ) -> ScriptOrigin { |
| ScriptOrigin { |
| code: SourceCode::Text(text), |
| url, |
| external: true, |
| fetch_options, |
| type_, |
| unminified_dir, |
| import_map: Err(Error::NotFound), |
| } |
| } |
| |
| pub(crate) fn text(&self) -> Rc<DOMString> { |
| match &self.code { |
| SourceCode::Text(text) => Rc::clone(text), |
| SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text), |
| } |
| } |
| } |
| |
| /// Final steps of <https://html.spec.whatwg.org/multipage/#prepare-the-script-element> |
| fn finish_fetching_a_classic_script( |
| elem: &HTMLScriptElement, |
| script_kind: ExternalScriptKind, |
| url: ServoUrl, |
| load: ScriptResult, |
| can_gc: CanGc, |
| ) { |
| // Step 33. The "steps to run when the result is ready" for each type of script in 33.2-33.5. |
| // of https://html.spec.whatwg.org/multipage/#prepare-the-script-element |
| let document; |
| |
| match script_kind { |
| ExternalScriptKind::Asap => { |
| document = elem.preparation_time_document.get().unwrap(); |
| document.asap_script_loaded(elem, load, can_gc) |
| }, |
| ExternalScriptKind::AsapInOrder => { |
| document = elem.preparation_time_document.get().unwrap(); |
| document.asap_in_order_script_loaded(elem, load, can_gc) |
| }, |
| ExternalScriptKind::Deferred => { |
| document = elem.parser_document.as_rooted(); |
| document.deferred_script_loaded(elem, load, can_gc); |
| }, |
| ExternalScriptKind::ParsingBlocking => { |
| document = elem.parser_document.as_rooted(); |
| document.pending_parsing_blocking_script_loaded(elem, load, can_gc); |
| }, |
| } |
| |
| document.finish_load(LoadType::Script(url), can_gc); |
| } |
| |
| pub(crate) type ScriptResult = Result<ScriptOrigin, NoTrace<NetworkError>>; |
| |
| /// The context required for asynchronously loading an external script source. |
| struct ClassicContext { |
| /// The element that initiated the request. |
| elem: Trusted<HTMLScriptElement>, |
| /// The kind of external script. |
| kind: ExternalScriptKind, |
| /// The (fallback) character encoding argument to the "fetch a classic |
| /// script" algorithm. |
| character_encoding: &'static Encoding, |
| /// The response body received to date. |
| data: Vec<u8>, |
| /// The response metadata received to date. |
| metadata: Option<Metadata>, |
| /// The initial URL requested. |
| url: ServoUrl, |
| /// Indicates whether the request failed, and why |
| status: Result<(), NetworkError>, |
| /// The fetch options of the script |
| fetch_options: ScriptFetchOptions, |
| /// Timing object for this resource |
| resource_timing: ResourceFetchTiming, |
| } |
| |
| impl FetchResponseListener for ClassicContext { |
| // TODO(KiChjang): Perhaps add custom steps to perform fetch here? |
| fn process_request_body(&mut self, _: RequestId) {} |
| |
| // TODO(KiChjang): Perhaps add custom steps to perform fetch here? |
| fn process_request_eof(&mut self, _: RequestId) {} |
| |
| fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) { |
| self.metadata = metadata.ok().map(|meta| match meta { |
| FetchMetadata::Unfiltered(m) => m, |
| FetchMetadata::Filtered { unsafe_, .. } => unsafe_, |
| }); |
| |
| let status = self |
| .metadata |
| .as_ref() |
| .map(|m| m.status.clone()) |
| .unwrap_or_else(HttpStatus::new_error); |
| |
| self.status = { |
| if status.is_error() { |
| Err(NetworkError::Internal( |
| "No http status code received".to_owned(), |
| )) |
| } else if status.is_success() { |
| Ok(()) |
| } else { |
| Err(NetworkError::Internal(format!( |
| "HTTP error code {}", |
| status.code() |
| ))) |
| } |
| }; |
| } |
| |
| fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) { |
| if self.status.is_ok() { |
| self.data.append(&mut chunk); |
| } |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> |
| /// step 4-9 |
| #[allow(unsafe_code)] |
| fn process_response_eof( |
| &mut self, |
| _: RequestId, |
| response: Result<ResourceFetchTiming, NetworkError>, |
| ) { |
| let (source_text, final_url) = match (response.as_ref(), self.status.as_ref()) { |
| (Err(err), _) | (_, Err(err)) => { |
| // Step 6, response is an error. |
| finish_fetching_a_classic_script( |
| &self.elem.root(), |
| self.kind, |
| self.url.clone(), |
| Err(NoTrace(err.clone())), |
| CanGc::note(), |
| ); |
| return; |
| }, |
| (Ok(_), Ok(_)) => { |
| let metadata = self.metadata.take().unwrap(); |
| |
| // Step 7. |
| let encoding = metadata |
| .charset |
| .and_then(|encoding| Encoding::for_label(encoding.as_bytes())) |
| .unwrap_or(self.character_encoding); |
| |
| // Step 8. |
| let (source_text, _, _) = encoding.decode(&self.data); |
| (source_text, metadata.final_url) |
| }, |
| }; |
| |
| let elem = self.elem.root(); |
| let global = elem.global(); |
| // let cx = GlobalScope::get_cx(); |
| let _ar = enter_realm(&*global); |
| |
| /* |
| let options = unsafe { CompileOptionsWrapper::new(*cx, final_url.as_str(), 1) }; |
| |
| let can_compile_off_thread = pref!(dom_script_asynch) && |
| unsafe { CanCompileOffThread(*cx, options.ptr as *const _, source_text.len()) }; |
| |
| if can_compile_off_thread { |
| let source_string = source_text.to_string(); |
| |
| let context = Box::new(OffThreadCompilationContext { |
| script_element: self.elem.clone(), |
| script_kind: self.kind, |
| final_url, |
| url: self.url.clone(), |
| task_source: elem.owner_global().task_manager().dom_manipulation_task_source(), |
| script_text: source_string, |
| fetch_options: self.fetch_options.clone(), |
| }); |
| |
| unsafe { |
| assert!(!CompileToStencilOffThread1( |
| *cx, |
| options.ptr as *const _, |
| &mut transform_str_to_source_text(&context.script_text) as *mut _, |
| Some(off_thread_compilation_callback), |
| Box::into_raw(context) as *mut c_void, |
| ) |
| .is_null()); |
| } |
| } else {*/ |
| let load = ScriptOrigin::external( |
| Rc::new(DOMString::from(source_text)), |
| final_url.clone(), |
| self.fetch_options.clone(), |
| ScriptType::Classic, |
| elem.parser_document.global().unminified_js_dir(), |
| ); |
| finish_fetching_a_classic_script( |
| &elem, |
| self.kind, |
| self.url.clone(), |
| Ok(load), |
| CanGc::note(), |
| ); |
| // } |
| } |
| |
| fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { |
| &mut self.resource_timing |
| } |
| |
| fn resource_timing(&self) -> &ResourceFetchTiming { |
| &self.resource_timing |
| } |
| |
| fn submit_resource_timing(&mut self) { |
| network_listener::submit_timing(self, CanGc::note()) |
| } |
| |
| fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) { |
| let global = &self.resource_timing_global(); |
| let elem = self.elem.root(); |
| let source_position = elem |
| .upcast::<Element>() |
| .compute_source_position(elem.line_number as u32); |
| global.report_csp_violations(violations, Some(elem.upcast()), Some(source_position)); |
| } |
| } |
| |
| impl ResourceTimingListener for ClassicContext { |
| fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { |
| let initiator_type = InitiatorType::LocalName( |
| self.elem |
| .root() |
| .upcast::<Element>() |
| .local_name() |
| .to_string(), |
| ); |
| (initiator_type, self.url.clone()) |
| } |
| |
| fn resource_timing_global(&self) -> DomRoot<GlobalScope> { |
| self.elem.root().owner_document().global() |
| } |
| } |
| |
| impl PreInvoke for ClassicContext {} |
| |
| /// Steps 1-2 of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> |
| // This function is also used to prefetch a script in `script::dom::servoparser::prefetch`. |
| #[allow(clippy::too_many_arguments)] |
| pub(crate) fn script_fetch_request( |
| webview_id: WebViewId, |
| url: ServoUrl, |
| cors_setting: Option<CorsSettings>, |
| origin: ImmutableOrigin, |
| pipeline_id: PipelineId, |
| options: ScriptFetchOptions, |
| insecure_requests_policy: InsecureRequestsPolicy, |
| has_trustworthy_ancestor_origin: bool, |
| policy_container: PolicyContainer, |
| ) -> RequestBuilder { |
| // We intentionally ignore options' credentials_mode member for classic scripts. |
| // The mode is initialized by create_a_potential_cors_request. |
| create_a_potential_cors_request( |
| Some(webview_id), |
| url, |
| Destination::Script, |
| cors_setting, |
| None, |
| options.referrer, |
| insecure_requests_policy, |
| has_trustworthy_ancestor_origin, |
| policy_container, |
| ) |
| .origin(origin) |
| .pipeline_id(Some(pipeline_id)) |
| .parser_metadata(options.parser_metadata) |
| .integrity_metadata(options.integrity_metadata.clone()) |
| .referrer_policy(options.referrer_policy) |
| .cryptographic_nonce_metadata(options.cryptographic_nonce) |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> |
| fn fetch_a_classic_script( |
| script: &HTMLScriptElement, |
| kind: ExternalScriptKind, |
| url: ServoUrl, |
| cors_setting: Option<CorsSettings>, |
| options: ScriptFetchOptions, |
| character_encoding: &'static Encoding, |
| ) { |
| // Step 1, 2. |
| let doc = script.owner_document(); |
| let global = script.global(); |
| let request = script_fetch_request( |
| doc.webview_id(), |
| url.clone(), |
| cors_setting, |
| doc.origin().immutable().clone(), |
| global.pipeline_id(), |
| options.clone(), |
| doc.insecure_requests_policy(), |
| doc.has_trustworthy_ancestor_origin(), |
| global.policy_container(), |
| ); |
| let request = doc.prepare_request(request); |
| |
| // TODO: Step 3, Add custom steps to perform fetch |
| |
| let context = ClassicContext { |
| elem: Trusted::new(script), |
| kind, |
| character_encoding, |
| data: vec![], |
| metadata: None, |
| url: url.clone(), |
| status: Ok(()), |
| fetch_options: options, |
| resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), |
| }; |
| doc.fetch(LoadType::Script(url), request, context); |
| } |
| |
| impl HTMLScriptElement { |
| /// <https://w3c.github.io/trusted-types/dist/spec/#setting-slot-values-from-parser> |
| pub(crate) fn set_initial_script_text(&self) { |
| *self.script_text.borrow_mut() = self.text(); |
| } |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-prepare-the-script-text> |
| fn prepare_the_script_text(&self, can_gc: CanGc) -> Fallible<()> { |
| // Step 1. If script’s script text value is not equal to its child text content, |
| // set script’s script text to the result of executing |
| // Get Trusted Type compliant string, with the following arguments: |
| if self.script_text.borrow().clone() != self.text() { |
| *self.script_text.borrow_mut() = TrustedScript::get_trusted_script_compliant_string( |
| &self.owner_global(), |
| self.Text(), |
| "HTMLScriptElement text", |
| can_gc, |
| )?; |
| } |
| |
| Ok(()) |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#prepare-the-script-element> |
| pub(crate) fn prepare(&self, introduction_type_override: Option<&'static CStr>, can_gc: CanGc) { |
| self.introduction_type_override |
| .set(introduction_type_override); |
| |
| // Step 1. If el's already started is true, then return. |
| if self.already_started.get() { |
| return; |
| } |
| |
| // Step 2. Let parser document be el's parser document. |
| // TODO |
| |
| // Step 3. Set el's parser document to null. |
| let was_parser_inserted = self.parser_inserted.get(); |
| self.parser_inserted.set(false); |
| |
| // Step 4. |
| // If parser document is non-null and el does not have an async attribute, then set el's force async to true. |
| let element = self.upcast::<Element>(); |
| let asynch = element.has_attribute(&local_name!("async")); |
| // Note: confusingly, this is done if the element does *not* have an "async" attribute. |
| if was_parser_inserted && !asynch { |
| self.non_blocking.set(true); |
| } |
| |
| // Step 5. Execute the Prepare the script text algorithm on el. |
| // If that algorithm threw an error, then return. |
| if self.prepare_the_script_text(can_gc).is_err() { |
| return; |
| } |
| // Step 5a. Let source text be el’s script text value. |
| let text = self.script_text.borrow().clone(); |
| // Step 6. If el has no src attribute, and source text is the empty string, then return. |
| if text.is_empty() && !element.has_attribute(&local_name!("src")) { |
| return; |
| } |
| |
| // Step 7. If el is not connected, then return. |
| if !self.upcast::<Node>().is_connected() { |
| return; |
| } |
| |
| let script_type = if let Some(ty) = self.get_script_type() { |
| // Step 9-11. |
| ty |
| } else { |
| // Step 12. Otherwise, return. (No script is executed, and el's type is left as null.) |
| return; |
| }; |
| |
| // Step 13. |
| // If parser document is non-null, then set el's parser document back to parser document and set el's force |
| // async to false. |
| if was_parser_inserted { |
| self.parser_inserted.set(true); |
| self.non_blocking.set(false); |
| } |
| |
| // Step 14. Set el's already started to true. |
| self.already_started.set(true); |
| |
| // Step 15. Set el's preparation-time document to its node document. |
| let doc = self.owner_document(); |
| self.preparation_time_document.set(Some(&doc)); |
| |
| // Step 16. |
| // If parser document is non-null, and parser document is not equal to el's preparation-time document, then |
| // return. |
| if self.parser_inserted.get() && *self.parser_document != *doc { |
| return; |
| } |
| |
| // Step 17. If scripting is disabled for el, then return. |
| if !doc.scripting_enabled() { |
| return; |
| } |
| |
| // Step 18. If el has a nomodule content attribute and its type is "classic", then return. |
| if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic { |
| return; |
| } |
| |
| let global = &doc.global(); |
| |
| // Step 19. CSP. |
| if !element.has_attribute(&local_name!("src")) && |
| global |
| .get_csp_list() |
| .should_elements_inline_type_behavior_be_blocked( |
| global, |
| element, |
| InlineCheckType::Script, |
| &text, |
| ) |
| { |
| warn!("Blocking inline script due to CSP"); |
| return; |
| } |
| |
| // Step 20. If el has an event attribute and a for attribute, and el's type is "classic", then: |
| if script_type == ScriptType::Classic { |
| let for_attribute = element.get_attribute(&ns!(), &local_name!("for")); |
| let event_attribute = element.get_attribute(&ns!(), &local_name!("event")); |
| if let (Some(ref for_attribute), Some(ref event_attribute)) = |
| (for_attribute, event_attribute) |
| { |
| let for_value = for_attribute.value().to_ascii_lowercase(); |
| let for_value = for_value.trim_matches(HTML_SPACE_CHARACTERS); |
| if for_value != "window" { |
| return; |
| } |
| |
| let event_value = event_attribute.value().to_ascii_lowercase(); |
| let event_value = event_value.trim_matches(HTML_SPACE_CHARACTERS); |
| if event_value != "onload" && event_value != "onload()" { |
| return; |
| } |
| } |
| } |
| |
| // Step 21. If el has a charset attribute, then let encoding be the result of getting |
| // an encoding from the value of the charset attribute. |
| // If el does not have a charset attribute, or if getting an encoding failed, |
| // then let encoding be el's node document's the encoding. |
| let encoding = element |
| .get_attribute(&ns!(), &local_name!("charset")) |
| .and_then(|charset| Encoding::for_label(charset.value().as_bytes())) |
| .unwrap_or_else(|| doc.encoding()); |
| |
| // Step 22. CORS setting. |
| let cors_setting = cors_setting_for_element(element); |
| |
| // Step 23. Module script credentials mode. |
| let module_credentials_mode = match script_type { |
| ScriptType::Classic => CredentialsMode::CredentialsSameOrigin, |
| ScriptType::Module | ScriptType::ImportMap => reflect_cross_origin_attribute(element) |
| .map_or( |
| CredentialsMode::CredentialsSameOrigin, |
| |attr| match &*attr { |
| "use-credentials" => CredentialsMode::Include, |
| "anonymous" => CredentialsMode::CredentialsSameOrigin, |
| _ => CredentialsMode::CredentialsSameOrigin, |
| }, |
| ), |
| }; |
| |
| // Step 24. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value. |
| let cryptographic_nonce = self.upcast::<Element>().nonce_value(); |
| |
| // Step 25. If el has an integrity attribute, then let integrity metadata be that attribute's value. |
| // Otherwise, let integrity metadata be the empty string. |
| let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity")); |
| let integrity_val = im_attribute.as_ref().map(|a| a.value()); |
| let integrity_metadata = match integrity_val { |
| Some(ref value) => &***value, |
| None => "", |
| }; |
| |
| // Step 26. Let referrer policy be the current state of el's referrerpolicy content attribute. |
| let referrer_policy = referrer_policy_for_element(self.upcast::<Element>()); |
| |
| // TODO: Step 27. Fetch priority. |
| |
| // Step 28. Let parser metadata be "parser-inserted" if el is parser-inserted, |
| // and "not-parser-inserted" otherwise. |
| let parser_metadata = if self.parser_inserted.get() { |
| ParserMetadata::ParserInserted |
| } else { |
| ParserMetadata::NotParserInserted |
| }; |
| |
| // Step 29. Fetch options. |
| let options = ScriptFetchOptions { |
| cryptographic_nonce, |
| integrity_metadata: integrity_metadata.to_owned(), |
| parser_metadata, |
| referrer: self.global().get_referrer(), |
| referrer_policy, |
| credentials_mode: module_credentials_mode, |
| }; |
| |
| // Step 30. Let settings object be el's node document's relevant settings object. |
| // This is done by passing ModuleOwner in step 31.11 and step 32.2. |
| // What we actually need is global's import map eventually. |
| |
| let base_url = doc.base_url(); |
| if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) { |
| // Step 31. If el has a src content attribute, then: |
| |
| // Step 31.1. If el's type is "importmap". |
| if script_type == ScriptType::ImportMap { |
| // then queue an element task on the DOM manipulation task source |
| // given el to fire an event named error at el, and return. |
| self.queue_error_event(); |
| return; |
| } |
| |
| // Step 31.2. Let src be the value of el's src attribute. |
| let src = src.value(); |
| |
| // Step 31.3. If src is the empty string. |
| if src.is_empty() { |
| self.queue_error_event(); |
| return; |
| } |
| |
| // Step 31.4. Set el's from an external file to true. |
| // The "from an external file"" flag is stored in ScriptOrigin. |
| |
| // Step 31.5-31.6. Parse URL. |
| let url = match base_url.join(&src) { |
| Ok(url) => url, |
| Err(_) => { |
| warn!("error parsing URL for script {}", &**src); |
| self.queue_error_event(); |
| return; |
| }, |
| }; |
| |
| // TODO: |
| // Step 31.7. If el is potentially render-blocking, then block rendering on el. |
| // Step 31.8. Set el's delaying the load event to true. |
| // Step 31.9. If el is currently render-blocking, then set options's render-blocking to true. |
| |
| // Step 31.11. Switch on el's type: |
| match script_type { |
| ScriptType::Classic => { |
| let kind = if element.has_attribute(&local_name!("defer")) && |
| was_parser_inserted && |
| !asynch |
| { |
| // Step 33.4: classic, has src, has defer, was parser-inserted, is not async. |
| ExternalScriptKind::Deferred |
| } else if was_parser_inserted && !asynch { |
| // Step 33.5: classic, has src, was parser-inserted, is not async. |
| ExternalScriptKind::ParsingBlocking |
| } else if !asynch && !self.non_blocking.get() { |
| // Step 33.3: classic, has src, is not async, is not non-blocking. |
| ExternalScriptKind::AsapInOrder |
| } else { |
| // Step 33.2: classic, has src. |
| ExternalScriptKind::Asap |
| }; |
| |
| // Step 31.11. Fetch a classic script. |
| fetch_a_classic_script(self, kind, url, cors_setting, options, encoding); |
| |
| // Step 33.2/33.3/33.4/33.5, substeps 1-2. Add el to the corresponding script list. |
| match kind { |
| ExternalScriptKind::Deferred => doc.add_deferred_script(self), |
| ExternalScriptKind::ParsingBlocking => { |
| doc.set_pending_parsing_blocking_script(self, None) |
| }, |
| ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self), |
| ExternalScriptKind::Asap => doc.add_asap_script(self), |
| } |
| }, |
| ScriptType::Module => { |
| // Step 31.11. Fetch an external module script graph. |
| fetch_external_module_script( |
| ModuleOwner::Window(Trusted::new(self)), |
| url.clone(), |
| Destination::Script, |
| options, |
| can_gc, |
| ); |
| |
| if !asynch && was_parser_inserted { |
| // 33.4: module, not async, parser-inserted |
| doc.add_deferred_script(self); |
| } else if !asynch && !self.non_blocking.get() { |
| // 33.3: module, not parser-inserted |
| doc.push_asap_in_order_script(self); |
| } else { |
| // 33.2: module, async |
| doc.add_asap_script(self); |
| }; |
| }, |
| ScriptType::ImportMap => (), |
| } |
| } else { |
| // Step 32. If el does not have a src content attribute: |
| |
| assert!(!text.is_empty()); |
| |
| let text_rc = Rc::new(text); |
| |
| // Step 32.2: Switch on el's type: |
| match script_type { |
| ScriptType::Classic => { |
| let result = Ok(ScriptOrigin::internal( |
| text_rc, |
| base_url, |
| options, |
| script_type, |
| self.global().unminified_js_dir(), |
| Err(Error::NotFound), |
| )); |
| |
| if was_parser_inserted && |
| doc.get_current_parser() |
| .is_some_and(|parser| parser.script_nesting_level() <= 1) && |
| doc.get_script_blocking_stylesheets_count() > 0 |
| { |
| // Step 34.2: classic, has no src, was parser-inserted, is blocked on stylesheet. |
| doc.set_pending_parsing_blocking_script(self, Some(result)); |
| } else { |
| // Step 34.3: otherwise. |
| self.execute(result, can_gc); |
| } |
| }, |
| ScriptType::Module => { |
| // We should add inline module script elements |
| // into those vectors in case that there's no |
| // descendants in the inline module script. |
| if !asynch && was_parser_inserted { |
| doc.add_deferred_script(self); |
| } else if !asynch && !self.non_blocking.get() { |
| doc.push_asap_in_order_script(self); |
| } else { |
| doc.add_asap_script(self); |
| }; |
| |
| fetch_inline_module_script( |
| ModuleOwner::Window(Trusted::new(self)), |
| text_rc, |
| base_url.clone(), |
| self.id, |
| options, |
| can_gc, |
| ); |
| }, |
| ScriptType::ImportMap => { |
| // Step 32.1 Let result be the result of creating an import map |
| // parse result given source text and base URL. |
| let import_map_result = parse_an_import_map_string( |
| ModuleOwner::Window(Trusted::new(self)), |
| Rc::clone(&text_rc), |
| base_url.clone(), |
| can_gc, |
| ); |
| let result = Ok(ScriptOrigin::internal( |
| text_rc, |
| base_url, |
| options, |
| script_type, |
| self.global().unminified_js_dir(), |
| import_map_result, |
| )); |
| |
| // Step 34.3 |
| self.execute(result, can_gc); |
| }, |
| } |
| } |
| } |
| |
| fn substitute_with_local_script(&self, script: &mut ScriptOrigin) { |
| if self |
| .parser_document |
| .window() |
| .local_script_source() |
| .is_none() || |
| !script.external |
| { |
| return; |
| } |
| let mut path = PathBuf::from( |
| self.parser_document |
| .window() |
| .local_script_source() |
| .clone() |
| .unwrap(), |
| ); |
| path = path.join(&script.url[url::Position::BeforeHost..]); |
| debug!("Attempting to read script stored at: {:?}", path); |
| match read_to_string(path.clone()) { |
| Ok(local_script) => { |
| debug!("Found script stored at: {:?}", path); |
| script.code = SourceCode::Text(Rc::new(DOMString::from(local_script))); |
| }, |
| Err(why) => warn!("Could not restore script from file {:?}", why), |
| } |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#execute-the-script-element> |
| pub(crate) fn execute(&self, result: ScriptResult, can_gc: CanGc) { |
| // Step 1. Let document be el's node document. |
| let doc = self.owner_document(); |
| |
| // Step 2. If el's preparation-time document is not equal to document, then return. |
| if *doc != *self.preparation_time_document.get().unwrap() { |
| return; |
| } |
| |
| // TODO: Step 3. Unblock rendering on el. |
| let mut script = match result { |
| // Step 4. If el's result is null, then fire an event named error at el, and return. |
| Err(e) => { |
| warn!("error loading script {:?}", e); |
| self.dispatch_error_event(can_gc); |
| return; |
| }, |
| |
| Ok(script) => script, |
| }; |
| |
| if script.type_ == ScriptType::Classic { |
| unminify_js(&mut script); |
| self.substitute_with_local_script(&mut script); |
| } |
| |
| // Step 5. |
| // If el's from an external file is true, or el's type is "module", then increment document's |
| // ignore-destructive-writes counter. |
| let neutralized_doc = if script.external || script.type_ == ScriptType::Module { |
| debug!("loading external script, url = {}", script.url); |
| let doc = self.owner_document(); |
| doc.incr_ignore_destructive_writes_counter(); |
| Some(doc) |
| } else { |
| None |
| }; |
| |
| // Step 6. |
| let document = self.owner_document(); |
| let old_script = document.GetCurrentScript(); |
| let introduction_type = |
| self.introduction_type_override |
| .get() |
| .unwrap_or(if script.external { |
| IntroductionType::SRC_SCRIPT |
| } else { |
| IntroductionType::INLINE_SCRIPT |
| }); |
| |
| match script.type_ { |
| ScriptType::Classic => { |
| if self.upcast::<Node>().is_in_a_shadow_tree() { |
| document.set_current_script(None) |
| } else { |
| document.set_current_script(Some(self)) |
| } |
| self.run_a_classic_script(&script, can_gc, Some(introduction_type)); |
| document.set_current_script(old_script.as_deref()); |
| }, |
| ScriptType::Module => { |
| document.set_current_script(None); |
| self.run_a_module_script(&script, false, can_gc); |
| }, |
| ScriptType::ImportMap => { |
| // Step 6.1 Register an import map given el's relevant global object and el's result. |
| register_import_map(&self.owner_global(), script.import_map, can_gc); |
| }, |
| } |
| |
| // Step 7. |
| // Decrement the ignore-destructive-writes counter of document, if it was incremented in the earlier step. |
| if let Some(doc) = neutralized_doc { |
| doc.decr_ignore_destructive_writes_counter(); |
| } |
| |
| // Step 8. If el's from an external file is true, then fire an event named load at el. |
| if script.external { |
| self.dispatch_load_event(can_gc); |
| } |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#run-a-classic-script |
| pub(crate) fn run_a_classic_script( |
| &self, |
| script: &ScriptOrigin, |
| can_gc: CanGc, |
| introduction_type: Option<&'static CStr>, |
| ) { |
| // TODO use a settings object rather than this element's document/window |
| // Step 2 |
| let document = self.owner_document(); |
| if !document.is_fully_active() || !document.scripting_enabled() { |
| return; |
| } |
| |
| // Steps 4-10 |
| let window = self.owner_window(); |
| let line_number = if script.external { |
| 1 |
| } else { |
| self.line_number as u32 |
| }; |
| rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue()); |
| _ = window |
| .as_global_scope() |
| .evaluate_script_on_global_with_result( |
| &script.code, |
| script.url.as_str(), |
| rval.handle_mut(), |
| line_number, |
| script.fetch_options.clone(), |
| script.url.clone(), |
| can_gc, |
| introduction_type, |
| ); |
| } |
| |
| #[allow(unsafe_code)] |
| /// <https://html.spec.whatwg.org/multipage/#run-a-module-script> |
| pub(crate) fn run_a_module_script( |
| &self, |
| script: &ScriptOrigin, |
| _rethrow_errors: bool, |
| can_gc: CanGc, |
| ) { |
| // TODO use a settings object rather than this element's document/window |
| // Step 2 |
| let document = self.owner_document(); |
| if !document.is_fully_active() || !document.scripting_enabled() { |
| return; |
| } |
| |
| // Step 4 |
| let window = self.owner_window(); |
| let global = window.as_global_scope(); |
| let _aes = AutoEntryScript::new(global); |
| |
| let tree = if script.external { |
| global.get_module_map().borrow().get(&script.url).cloned() |
| } else { |
| global |
| .get_inline_module_map() |
| .borrow() |
| .get(&self.id.clone()) |
| .cloned() |
| }; |
| |
| if let Some(module_tree) = tree { |
| // Step 6. |
| { |
| let module_error = module_tree.get_rethrow_error().borrow(); |
| let network_error = module_tree.get_network_error().borrow(); |
| if module_error.is_some() && network_error.is_none() { |
| module_tree.report_error(global, can_gc); |
| return; |
| } |
| } |
| |
| let record = module_tree |
| .get_record() |
| .borrow() |
| .as_ref() |
| .map(|record| record.handle()); |
| |
| if let Some(record) = record { |
| rooted!(in(*GlobalScope::get_cx()) let mut rval = UndefinedValue()); |
| let evaluated = |
| module_tree.execute_module(global, record, rval.handle_mut().into(), can_gc); |
| |
| if let Err(exception) = evaluated { |
| module_tree.set_rethrow_error(exception); |
| module_tree.report_error(global, can_gc); |
| } |
| } |
| } |
| } |
| |
| pub(crate) fn queue_error_event(&self) { |
| self.owner_global() |
| .task_manager() |
| .dom_manipulation_task_source() |
| .queue_simple_event(self.upcast(), atom!("error")); |
| } |
| |
| pub(crate) fn dispatch_load_event(&self, can_gc: CanGc) { |
| self.dispatch_event( |
| atom!("load"), |
| EventBubbles::DoesNotBubble, |
| EventCancelable::NotCancelable, |
| can_gc, |
| ); |
| } |
| |
| pub(crate) fn dispatch_error_event(&self, can_gc: CanGc) { |
| self.dispatch_event( |
| atom!("error"), |
| EventBubbles::DoesNotBubble, |
| EventCancelable::NotCancelable, |
| can_gc, |
| ); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#prepare-a-script Step 7. |
| pub(crate) fn get_script_type(&self) -> Option<ScriptType> { |
| let element = self.upcast::<Element>(); |
| |
| let type_attr = element.get_attribute(&ns!(), &local_name!("type")); |
| let language_attr = element.get_attribute(&ns!(), &local_name!("language")); |
| |
| match ( |
| type_attr.as_ref().map(|t| t.value()), |
| language_attr.as_ref().map(|l| l.value()), |
| ) { |
| (Some(ref ty), _) if ty.is_empty() => { |
| debug!("script type empty, inferring js"); |
| Some(ScriptType::Classic) |
| }, |
| (None, Some(ref lang)) if lang.is_empty() => { |
| debug!("script type empty, inferring js"); |
| Some(ScriptType::Classic) |
| }, |
| (None, None) => { |
| debug!("script type empty, inferring js"); |
| Some(ScriptType::Classic) |
| }, |
| (None, Some(ref lang)) => { |
| debug!("script language={}", &***lang); |
| let language = format!("text/{}", &***lang); |
| |
| if SCRIPT_JS_MIMES.contains(&language.to_ascii_lowercase().as_str()) { |
| Some(ScriptType::Classic) |
| } else { |
| None |
| } |
| }, |
| (Some(ref ty), _) => { |
| debug!("script type={}", &***ty); |
| |
| if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "module" { |
| return Some(ScriptType::Module); |
| } |
| |
| if ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS) == "importmap" { |
| return Some(ScriptType::ImportMap); |
| } |
| |
| if SCRIPT_JS_MIMES |
| .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS)) |
| { |
| Some(ScriptType::Classic) |
| } else { |
| None |
| } |
| }, |
| } |
| } |
| |
| pub(crate) fn set_parser_inserted(&self, parser_inserted: bool) { |
| self.parser_inserted.set(parser_inserted); |
| } |
| |
| pub(crate) fn get_parser_inserted(&self) -> bool { |
| self.parser_inserted.get() |
| } |
| |
| pub(crate) fn set_already_started(&self, already_started: bool) { |
| self.already_started.set(already_started); |
| } |
| |
| pub(crate) fn get_non_blocking(&self) -> bool { |
| self.non_blocking.get() |
| } |
| |
| fn dispatch_event( |
| &self, |
| type_: Atom, |
| bubbles: EventBubbles, |
| cancelable: EventCancelable, |
| can_gc: CanGc, |
| ) -> bool { |
| let window = self.owner_window(); |
| let event = Event::new(window.upcast(), type_, bubbles, cancelable, can_gc); |
| event.fire(self.upcast(), can_gc) |
| } |
| |
| fn text(&self) -> DOMString { |
| match self.Text() { |
| TrustedScriptOrString::String(value) => value, |
| TrustedScriptOrString::TrustedScript(trusted_script) => { |
| DOMString::from(trusted_script.to_string()) |
| }, |
| } |
| } |
| } |
| |
| impl VirtualMethods for HTMLScriptElement { |
| fn super_type(&self) -> Option<&dyn VirtualMethods> { |
| Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) |
| } |
| |
| fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { |
| self.super_type() |
| .unwrap() |
| .attribute_mutated(attr, mutation, can_gc); |
| if *attr.local_name() == local_name!("src") { |
| if let AttributeMutation::Set(_) = mutation { |
| if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { |
| self.prepare(Some(IntroductionType::INJECTED_SCRIPT), can_gc); |
| } |
| } |
| } |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-26> |
| fn children_changed(&self, mutation: &ChildrenMutation) { |
| if let Some(s) = self.super_type() { |
| s.children_changed(mutation); |
| } |
| |
| if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() { |
| let script = DomRoot::from_ref(self); |
| // This method can be invoked while there are script/layout blockers present |
| // as DOM mutations have not yet settled. We use a delayed task to avoid |
| // running any scripts until the DOM tree is safe for interactions. |
| self.owner_document().add_delayed_task( |
| task!(ScriptPrepare: |script: DomRoot<HTMLScriptElement>| { |
| script.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note()); |
| }), |
| ); |
| } |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#script-processing-model:the-script-element-20> |
| fn post_connection_steps(&self) { |
| if let Some(s) = self.super_type() { |
| s.post_connection_steps(); |
| } |
| |
| if self.upcast::<Node>().is_connected() && !self.parser_inserted.get() { |
| self.prepare(Some(IntroductionType::INJECTED_SCRIPT), CanGc::note()); |
| } |
| } |
| |
| fn cloning_steps( |
| &self, |
| copy: &Node, |
| maybe_doc: Option<&Document>, |
| clone_children: CloneChildrenFlag, |
| can_gc: CanGc, |
| ) { |
| if let Some(s) = self.super_type() { |
| s.cloning_steps(copy, maybe_doc, clone_children, can_gc); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#already-started |
| if self.already_started.get() { |
| copy.downcast::<HTMLScriptElement>() |
| .unwrap() |
| .set_already_started(true); |
| } |
| } |
| } |
| |
| impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement { |
| // https://html.spec.whatwg.org/multipage/#dom-script-src |
| fn Src(&self) -> TrustedScriptURLOrUSVString { |
| let element = self.upcast::<Element>(); |
| element.get_trusted_type_url_attribute(&local_name!("src")) |
| } |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#the-src-idl-attribute> |
| fn SetSrc(&self, value: TrustedScriptURLOrUSVString, can_gc: CanGc) -> Fallible<()> { |
| let element = self.upcast::<Element>(); |
| let local_name = &local_name!("src"); |
| let value = TrustedScriptURL::get_trusted_script_url_compliant_string( |
| &element.owner_global(), |
| value, |
| "HTMLScriptElement", |
| local_name, |
| can_gc, |
| )?; |
| element.set_attribute( |
| local_name, |
| AttrValue::String(value.as_ref().to_owned()), |
| can_gc, |
| ); |
| Ok(()) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-type |
| make_getter!(Type, "type"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-type |
| make_setter!(SetType, "type"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-charset |
| make_getter!(Charset, "charset"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-charset |
| make_setter!(SetCharset, "charset"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-async |
| fn Async(&self) -> bool { |
| self.non_blocking.get() || |
| self.upcast::<Element>() |
| .has_attribute(&local_name!("async")) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-async |
| fn SetAsync(&self, value: bool, can_gc: CanGc) { |
| self.non_blocking.set(false); |
| self.upcast::<Element>() |
| .set_bool_attribute(&local_name!("async"), value, can_gc); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-defer |
| make_bool_getter!(Defer, "defer"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-defer |
| make_bool_setter!(SetDefer, "defer"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-nomodule |
| make_bool_getter!(NoModule, "nomodule"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-nomodule |
| make_bool_setter!(SetNoModule, "nomodule"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-integrity |
| make_getter!(Integrity, "integrity"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-integrity |
| make_setter!(SetIntegrity, "integrity"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-event |
| make_getter!(Event, "event"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-event |
| make_setter!(SetEvent, "event"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor |
| make_getter!(HtmlFor, "for"); |
| // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor |
| make_setter!(SetHtmlFor, "for"); |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin |
| fn GetCrossOrigin(&self) -> Option<DOMString> { |
| reflect_cross_origin_attribute(self.upcast::<Element>()) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin |
| fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) { |
| set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy |
| fn ReferrerPolicy(&self) -> DOMString { |
| reflect_referrer_policy_attribute(self.upcast::<Element>()) |
| } |
| |
| // https://html.spec.whatwg.org/multipage/#dom-script-referrerpolicy |
| make_setter!(SetReferrerPolicy, "referrerpolicy"); |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#dom-htmlscriptelement-innertext> |
| fn InnerText(&self) -> TrustedScriptOrString { |
| // Step 1: Return the result of running get the text steps with this. |
| TrustedScriptOrString::String(self.upcast::<HTMLElement>().get_inner_outer_text()) |
| } |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#the-innerText-idl-attribute> |
| fn SetInnerText(&self, input: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> { |
| // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript, |
| // this's relevant global object, the given value, HTMLScriptElement innerText, and script. |
| let value = TrustedScript::get_trusted_script_compliant_string( |
| &self.owner_global(), |
| input, |
| "HTMLScriptElement innerText", |
| can_gc, |
| )?; |
| *self.script_text.borrow_mut() = value.clone(); |
| // Step 3: Run set the inner text steps with this and value. |
| self.upcast::<HTMLElement>().set_inner_text(value, can_gc); |
| Ok(()) |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#dom-script-text> |
| fn Text(&self) -> TrustedScriptOrString { |
| TrustedScriptOrString::String(self.upcast::<Node>().child_text_content()) |
| } |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#the-text-idl-attribute> |
| fn SetText(&self, value: TrustedScriptOrString, can_gc: CanGc) -> Fallible<()> { |
| // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript, |
| // this's relevant global object, the given value, HTMLScriptElement text, and script. |
| let value = TrustedScript::get_trusted_script_compliant_string( |
| &self.owner_global(), |
| value, |
| "HTMLScriptElement text", |
| can_gc, |
| )?; |
| // Step 2: Set this's script text value to the given value. |
| *self.script_text.borrow_mut() = value.clone(); |
| // Step 3: String replace all with the given value within this. |
| Node::string_replace_all(value, self.upcast::<Node>(), can_gc); |
| Ok(()) |
| } |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute> |
| fn GetTextContent(&self) -> Option<TrustedScriptOrString> { |
| // Step 1: Return the result of running get text content with this. |
| Some(TrustedScriptOrString::String( |
| self.upcast::<Node>().GetTextContent()?, |
| )) |
| } |
| |
| /// <https://w3c.github.io/trusted-types/dist/spec/#the-textContent-idl-attribute> |
| fn SetTextContent(&self, value: Option<TrustedScriptOrString>, can_gc: CanGc) -> Fallible<()> { |
| // Step 1: Let value be the result of calling Get Trusted Type compliant string with TrustedScript, |
| // this's relevant global object, the given value, HTMLScriptElement textContent, and script. |
| let value = TrustedScript::get_trusted_script_compliant_string( |
| &self.owner_global(), |
| value.unwrap_or(TrustedScriptOrString::String(DOMString::from(""))), |
| "HTMLScriptElement textContent", |
| can_gc, |
| )?; |
| // Step 2: Set this's script text value to value. |
| *self.script_text.borrow_mut() = value.clone(); |
| // Step 3: Run set text content with this and value. |
| self.upcast::<Node>() |
| .set_text_content_for_element(Some(value), can_gc); |
| Ok(()) |
| } |
| |
| /// <https://html.spec.whatwg.org/multipage/#dom-script-supports> |
| fn Supports(_window: &Window, type_: DOMString) -> bool { |
| // The type argument has to exactly match these values, |
| // we do not perform an ASCII case-insensitive match. |
| matches!(type_.str(), "classic" | "module" | "importmap") |
| } |
| } |
| |
| #[derive(Clone, Copy)] |
| enum ExternalScriptKind { |
| Deferred, |
| ParsingBlocking, |
| AsapInOrder, |
| Asap, |
| } |