diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom/htmlscriptelement.rs | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/dom/htmlscriptelement.rs')
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 1259 |
1 files changed, 934 insertions, 325 deletions
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index d6679c5168c..d27fee98fb5 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -1,100 +1,222 @@ /* 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 http://mozilla.org/MPL/2.0/. */ - -use document_loader::LoadType; -use dom::attr::Attr; -use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; -use dom::bindings::codegen::Bindings::HTMLScriptElementBinding; -use dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods; -use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{JS, Root}; -use dom::bindings::js::RootedReference; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::document::Document; -use dom::element::{AttributeMutation, Element, ElementCreator}; -use dom::element::{cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute}; -use dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{ChildrenMutation, CloneChildrenFlag, Node}; -use dom::node::{document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::LoadType; +use crate::dom::attr::Attr; +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::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::settings_stack::AutoEntryScript; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::document::Document; +use crate::dom::element::{ + cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute, + reflect_referrer_policy_attribute, set_cross_origin_attribute, +}; +use crate::dom::element::{AttributeMutation, Element, ElementCreator}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node}; +use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::virtualmethods::VirtualMethods; +use crate::fetch::create_a_potential_cors_request; +use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; +use crate::realms::enter_realm; +use crate::script_module::fetch_inline_module_script; +use crate::script_module::{fetch_external_module_script, ModuleOwner, ScriptFetchOptions}; +use crate::task::TaskCanceller; +use crate::task_source::dom_manipulation::DOMManipulationTaskSource; +use crate::task_source::TaskSource; +use crate::task_source::TaskSourceName; +use content_security_policy as csp; +use core::ffi::c_void; use dom_struct::dom_struct; -use encoding::label::encoding_from_whatwg_label; -use encoding::types::{DecoderTrap, EncodingRef}; -use html5ever_atoms::LocalName; +use encoding_rs::Encoding; +use html5ever::{LocalName, Prefix}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; +use js::jsapi::{ + CanCompileOffThread, CompileOffThread1, FinishOffThreadScript, Heap, JSScript, OffThreadToken, +}; use js::jsval::UndefinedValue; -use net_traits::{FetchMetadata, FetchResponseListener, Metadata, NetworkError}; -use net_traits::request::{CorsSettings, CredentialsMode, Destination, RequestInit, RequestMode, Type as RequestType}; -use network_listener::{NetworkListener, PreInvoke}; +use js::rust::{transform_str_to_source_text, CompileOptionsWrapper}; +use msg::constellation_msg::PipelineId; +use net_traits::request::{ + CorsSettings, CredentialsMode, Destination, ParserMetadata, RequestBuilder, +}; +use net_traits::{ + FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming, + ResourceTimingType, +}; use servo_atoms::Atom; -use servo_config::opts; +use servo_config::pref; +use servo_url::ImmutableOrigin; use servo_url::ServoUrl; -use std::ascii::AsciiExt; use std::cell::Cell; -use std::fs::File; -use std::io::{Read, Write}; +use std::fs::{create_dir_all, read_to_string, File}; +use std::io::{Read, Seek, Write}; +use std::mem::replace; use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::process::Command; +use std::rc::Rc; use std::sync::{Arc, Mutex}; -use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec}; +use style::str::{StaticStringVec, HTML_SPACE_CHARACTERS}; use uuid::Uuid; +pub struct OffThreadCompilationContext { + script_element: Trusted<HTMLScriptElement>, + script_kind: ExternalScriptKind, + final_url: ServoUrl, + url: ServoUrl, + task_source: DOMManipulationTaskSource, + canceller: TaskCanceller, + script_text: String, + fetch_options: ScriptFetchOptions, +} + +/// A wrapper to mark OffThreadToken as Send, +/// which should be safe according to +/// mozjs/js/public/OffThreadScriptCompilation.h +struct OffThreadCompilationToken(*mut OffThreadToken); + +#[allow(unsafe_code)] +unsafe impl Send for OffThreadCompilationToken {} + +#[allow(unsafe_code)] +unsafe extern "C" fn off_thread_compilation_callback( + token: *mut OffThreadToken, + callback_data: *mut c_void, +) { + let mut context = Box::from_raw(callback_data as *mut OffThreadCompilationContext); + let token = OffThreadCompilationToken(token); + + let url = context.url.clone(); + let final_url = context.final_url.clone(); + let script_element = context.script_element.clone(); + let script_kind = context.script_kind.clone(); + let script = replace(&mut context.script_text, String::new()); + let fetch_options = context.fetch_options.clone(); + + // Continue with <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> + let _ = context.task_source.queue_with_canceller( + task!(off_thread_compile_continue: move || { + let elem = script_element.root(); + let global = elem.global(); + let cx = global.get_cx(); + let _ar = enter_realm(&*global); + + rooted!(in(*cx) + let compiled_script = FinishOffThreadScript(*cx, token.0) + ); + + let load = if compiled_script.get().is_null() { + Err(NetworkError::Internal( + "Off-thread compilation failed.".into(), + )) + } else { + let script_text = DOMString::from(script); + let heap = Heap::default(); + let source_code = RootedTraceableBox::new(heap); + source_code.set(compiled_script.get()); + let code = SourceCode::Compiled(CompiledSourceCode { + source_code: source_code, + original_text: Rc::new(script_text), + }); + + Ok(ScriptOrigin { + code, + url: final_url, + external: true, + fetch_options: fetch_options, + type_: ScriptType::Classic, + }) + }; + + finish_fetching_a_classic_script(&*elem, script_kind, url, load); + }), + &context.canceller, + ); +} + +/// An unique id for script element. +#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)] +pub struct ScriptId(Uuid); + #[dom_struct] pub struct HTMLScriptElement { htmlelement: HTMLElement, - /// https://html.spec.whatwg.org/multipage/#already-started + /// <https://html.spec.whatwg.org/multipage/#already-started> already_started: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#parser-inserted + /// <https://html.spec.whatwg.org/multipage/#parser-inserted> parser_inserted: Cell<bool>, - /// https://html.spec.whatwg.org/multipage/#non-blocking + /// <https://html.spec.whatwg.org/multipage/#non-blocking> /// /// (currently unused) non_blocking: Cell<bool>, /// Document of the parser that created this element - parser_document: JS<Document>, + parser_document: Dom<Document>, /// Track line line_number line_number: u64, + + /// Unique id for each script element + #[ignore_malloc_size_of = "Defined in uuid"] + id: ScriptId, } impl HTMLScriptElement { - fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document, - creator: ElementCreator) -> HTMLScriptElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> HTMLScriptElement { HTMLScriptElement { - htmlelement: - HTMLElement::new_inherited(local_name, prefix, document), + 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: JS::from_ref(document), + parser_document: Dom::from_ref(document), line_number: creator.return_line_number(), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, prefix: Option<DOMString>, document: &Document, - creator: ElementCreator) -> Root<HTMLScriptElement> { - Node::reflect_node(box HTMLScriptElement::new_inherited(local_name, prefix, document, creator), - document, - HTMLScriptElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + creator: ElementCreator, + ) -> DomRoot<HTMLScriptElement> { + Node::reflect_node( + Box::new(HTMLScriptElement::new_inherited( + local_name, prefix, document, creator, + )), + document, + ) } -} + pub fn get_script_id(&self) -> ScriptId { + self.id.clone() + } +} /// Supported script types as defined by /// <https://html.spec.whatwg.org/multipage/#javascript-mime-type>. -static SCRIPT_JS_MIMES: StaticStringVec = &[ +pub static SCRIPT_JS_MIMES: StaticStringVec = &[ "application/ecmascript", "application/javascript", "application/x-ecmascript", @@ -113,42 +235,110 @@ static SCRIPT_JS_MIMES: StaticStringVec = &[ "text/x-javascript", ]; -#[derive(HeapSizeOf, JSTraceable)] -pub struct ClassicScript { - text: DOMString, +#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +pub enum ScriptType { + Classic, + Module, +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct CompiledSourceCode { + #[ignore_malloc_size_of = "SM handles JS values"] + pub source_code: RootedTraceableBox<Heap<*mut JSScript>>, + #[ignore_malloc_size_of = "Rc is hard"] + pub original_text: Rc<DOMString>, +} + +#[derive(JSTraceable)] +pub enum SourceCode { + Text(Rc<DOMString>), + Compiled(CompiledSourceCode), +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct ScriptOrigin { + #[ignore_malloc_size_of = "Rc is hard"] + code: SourceCode, url: ServoUrl, external: bool, + fetch_options: ScriptFetchOptions, + type_: ScriptType, } -impl ClassicScript { - fn internal(text: DOMString, url: ServoUrl) -> ClassicScript { - ClassicScript { - text: text, +impl ScriptOrigin { + pub fn internal( + text: Rc<DOMString>, + url: ServoUrl, + fetch_options: ScriptFetchOptions, + type_: ScriptType, + ) -> ScriptOrigin { + ScriptOrigin { + code: SourceCode::Text(text), url: url, external: false, + fetch_options, + type_, } } - fn external(text: DOMString, url: ServoUrl) -> ClassicScript { - ClassicScript { - text: text, + pub fn external( + text: Rc<DOMString>, + url: ServoUrl, + fetch_options: ScriptFetchOptions, + type_: ScriptType, + ) -> ScriptOrigin { + ScriptOrigin { + code: SourceCode::Text(text), url: url, external: true, + fetch_options, + type_, + } + } + + pub fn text(&self) -> Rc<DOMString> { + match &self.code { + SourceCode::Text(text) => Rc::clone(&text), + SourceCode::Compiled(compiled_script) => Rc::clone(&compiled_script.original_text), } } } -pub type ScriptResult = Result<ClassicScript, NetworkError>; +/// Final steps of <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> +fn finish_fetching_a_classic_script( + elem: &HTMLScriptElement, + script_kind: ExternalScriptKind, + url: ServoUrl, + load: ScriptResult, +) { + // Step 11, Asynchronously complete this algorithm with script, + // which refers to step 26.6 "When the chosen algorithm asynchronously completes", + // of https://html.spec.whatwg.org/multipage/#prepare-a-script + let document = document_from_node(&*elem); + + match script_kind { + ExternalScriptKind::Asap => document.asap_script_loaded(&elem, load), + ExternalScriptKind::AsapInOrder => document.asap_in_order_script_loaded(&elem, load), + ExternalScriptKind::Deferred => document.deferred_script_loaded(&elem, load), + ExternalScriptKind::ParsingBlocking => { + document.pending_parsing_blocking_script_loaded(&elem, load) + }, + } + + document.finish_load(LoadType::Script(url)); +} + +pub type ScriptResult = Result<ScriptOrigin, NetworkError>; /// The context required for asynchronously loading an external script source. -struct ScriptContext { +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: EncodingRef, + character_encoding: &'static Encoding, /// The response body received to date. data: Vec<u8>, /// The response metadata received to date. @@ -156,32 +346,42 @@ struct ScriptContext { /// The initial URL requested. url: ServoUrl, /// Indicates whether the request failed, and why - status: Result<(), NetworkError> + status: Result<(), NetworkError>, + /// The fetch options of the script + fetch_options: ScriptFetchOptions, + /// Timing object for this resource + resource_timing: ResourceFetchTiming, } -impl FetchResponseListener for ScriptContext { +impl FetchResponseListener for ClassicContext { fn process_request_body(&mut self) {} // TODO(KiChjang): Perhaps add custom steps to perform fetch here? fn process_request_eof(&mut self) {} // TODO(KiChjang): Perhaps add custom steps to perform fetch here? - fn process_response(&mut self, - metadata: Result<FetchMetadata, NetworkError>) { + fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { self.metadata = metadata.ok().map(|meta| match meta { FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ + FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }); - let status_code = self.metadata.as_ref().and_then(|m| { - match m.status { + let status_code = self + .metadata + .as_ref() + .and_then(|m| match m.status { Some((c, _)) => Some(c), _ => None, - } - }).unwrap_or(0); + }) + .unwrap_or(0); self.status = match status_code { - 0 => Err(NetworkError::Internal("No http status code received".to_owned())), - 200...299 => Ok(()), // HTTP ok status codes - _ => Err(NetworkError::Internal(format!("HTTP error code {}", status_code))) + 0 => Err(NetworkError::Internal( + "No http status code received".to_owned(), + )), + 200..=299 => Ok(()), // HTTP ok status codes + _ => Err(NetworkError::Internal(format!( + "HTTP error code {}", + status_code + ))), }; } @@ -191,103 +391,194 @@ impl FetchResponseListener for ScriptContext { } } - /// https://html.spec.whatwg.org/multipage/#fetch-a-classic-script + /// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-script> /// step 4-9 - fn process_response_eof(&mut self, response: Result<(), NetworkError>) { - // Step 5. - let load = response.and(self.status.clone()).map(|_| { - let metadata = self.metadata.take().unwrap(); + #[allow(unsafe_code)] + fn process_response_eof(&mut self, 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.clone(), + self.url.clone(), + Err(err.clone()), + ); + 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) + }, + }; - // Step 6. - let encoding = metadata.charset - .and_then(|encoding| encoding_from_whatwg_label(&encoding)) - .unwrap_or(self.character_encoding); + let elem = self.elem.root(); + let global = elem.global(); + let cx = global.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.clone(), + final_url, + url: self.url.clone(), + task_source: global.dom_manipulation_task_source(), + canceller: global.task_canceller(TaskSourceName::DOMManipulation), + script_text: source_string, + fetch_options: self.fetch_options.clone(), + }); + + unsafe { + assert!(!CompileOffThread1( + *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, + ); + finish_fetching_a_classic_script(&*elem, self.kind.clone(), self.url.clone(), Ok(load)); + } + } - // Step 7. - let source_text = encoding.decode(&self.data, DecoderTrap::Replace).unwrap(); - ClassicScript::external(DOMString::from(source_text), metadata.final_url) - }); + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } - // Step 9. - // https://html.spec.whatwg.org/multipage/#prepare-a-script - // Step 18.6 (When the chosen algorithm asynchronously completes). - let elem = self.elem.root(); - let document = document_from_node(&*elem); + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } - match self.kind { - ExternalScriptKind::Asap => document.asap_script_loaded(&elem, load), - ExternalScriptKind::AsapInOrder => document.asap_in_order_script_loaded(&elem, load), - ExternalScriptKind::Deferred => document.deferred_script_loaded(&elem, load), - ExternalScriptKind::ParsingBlocking => document.pending_parsing_blocking_script_loaded(&elem, load), - } + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self) + } +} - document.finish_load(LoadType::Script(self.url.clone())); +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> { + document_from_node(&*self.elem.root()).global() } } -impl PreInvoke for ScriptContext {} +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`. +pub(crate) fn script_fetch_request( + url: ServoUrl, + cors_setting: Option<CorsSettings>, + origin: ImmutableOrigin, + pipeline_id: PipelineId, + options: ScriptFetchOptions, +) -> 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( + url, + Destination::Script, + cors_setting, + None, + options.referrer, + ) + .origin(origin) + .pipeline_id(Some(pipeline_id)) + .parser_metadata(options.parser_metadata) + .integrity_metadata(options.integrity_metadata.clone()) + .referrer_policy(options.referrer_policy) +} -/// 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>, - integrity_metadata: String, - character_encoding: EncodingRef) { +/// <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, +) { let doc = document_from_node(script); // Step 1, 2. - let request = RequestInit { - url: url.clone(), - type_: RequestType::Script, - destination: Destination::Script, - // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request - // Step 1 - mode: match cors_setting { - Some(_) => RequestMode::CorsMode, - None => RequestMode::NoCors, - }, - // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request - // Step 3-4 - credentials_mode: match cors_setting { - Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin, - _ => CredentialsMode::Include, - }, - origin: doc.url(), - pipeline_id: Some(script.global().pipeline_id()), - referrer_url: Some(doc.url()), - referrer_policy: doc.get_referrer_policy(), - integrity_metadata: integrity_metadata, - .. RequestInit::default() - }; + let request = script_fetch_request( + url.clone(), + cors_setting, + doc.origin().immutable().clone(), + script.global().pipeline_id(), + options.clone(), + ); // TODO: Step 3, Add custom steps to perform fetch - let context = Arc::new(Mutex::new(ScriptContext { + let context = Arc::new(Mutex::new(ClassicContext { elem: Trusted::new(script), kind: kind, character_encoding: character_encoding, - data: vec!(), + data: vec![], metadata: None, url: url.clone(), - status: Ok(()) + status: Ok(()), + fetch_options: options, + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), })); let (action_sender, action_receiver) = ipc::channel().unwrap(); + let (task_source, canceller) = doc + .window() + .task_manager() + .networking_task_source_with_canceller(); let listener = NetworkListener { - context: context, - task_source: doc.window().networking_task_source(), - wrapper: Some(doc.window().get_runnable_wrapper()) + context, + task_source, + canceller: Some(canceller), }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); - }); + ROUTER.add_route( + action_receiver.to_opaque(), + Box::new(move |message| { + listener.notify_fetch(message.to().unwrap()); + }), + ); doc.fetch_async(LoadType::Script(url), request, action_sender); } impl HTMLScriptElement { - /// https://html.spec.whatwg.org/multipage/#prepare-a-script + /// <https://html.spec.whatwg.org/multipage/#prepare-a-script> pub fn prepare(&self) { // Step 1. if self.already_started.get() { @@ -298,112 +589,160 @@ impl HTMLScriptElement { let was_parser_inserted = self.parser_inserted.get(); self.parser_inserted.set(false); - // Step 3. + // Step 4. let element = self.upcast::<Element>(); - let async = element.has_attribute(&local_name!("async")); + 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 && !async { + if was_parser_inserted && !asynch { self.non_blocking.set(true); } - // Step 4. + // Step 5-6. let text = self.Text(); if text.is_empty() && !element.has_attribute(&local_name!("src")) { return; } - // Step 5. - if !self.upcast::<Node>().is_in_doc() { + // Step 7. + if !self.upcast::<Node>().is_connected() { return; } - // Step 6. - if !self.is_javascript() { + let script_type = if let Some(ty) = self.get_script_type() { + ty + } else { + // Step 7. return; - } + }; - // Step 7. + // Step 8. if was_parser_inserted { self.parser_inserted.set(true); self.non_blocking.set(false); } - // Step 8. + // Step 10. self.already_started.set(true); - // Step 9. + // Step 12. let doc = document_from_node(self); if self.parser_inserted.get() && &*self.parser_document != &*doc { return; } - // Step 10. + // Step 13. if !doc.is_scripting_enabled() { return; } - // TODO(#4577): Step 11: CSP. + // Step 14 + if element.has_attribute(&local_name!("nomodule")) && script_type == ScriptType::Classic { + return; + } - // Step 12. - let for_attribute = element.get_attribute(&ns!(), &local_name!("for")); - let event_attribute = element.get_attribute(&ns!(), &local_name!("event")); - match (for_attribute.r(), event_attribute.r()) { - (Some(for_attribute), Some(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; - } + // Step 15. + if !element.has_attribute(&local_name!("src")) && + doc.should_elements_inline_type_behavior_be_blocked( + &element, + csp::InlineCheckType::Script, + &text, + ) == csp::CheckResult::Blocked + { + warn!("Blocking inline script due to CSP"); + 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 16. + 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")); + match (for_attribute, event_attribute) { + (Some(ref for_attribute), Some(ref 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 13. - let encoding = element.get_attribute(&ns!(), &local_name!("charset")) - .and_then(|charset| encoding_from_whatwg_label(&charset.value())) - .unwrap_or_else(|| doc.encoding()); + // Step 17. + 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 14. + // Step 18. let cors_setting = cors_setting_for_element(element); - // TODO: Step 15: Module script credentials mode. + // Step 19. + let module_credentials_mode = match script_type { + ScriptType::Classic => CredentialsMode::CredentialsSameOrigin, + ScriptType::Module => reflect_cross_origin_attribute(element).map_or( + CredentialsMode::CredentialsSameOrigin, + |attr| match &*attr { + "use-credentials" => CredentialsMode::Include, + "anonymous" => CredentialsMode::CredentialsSameOrigin, + _ => CredentialsMode::CredentialsSameOrigin, + }, + ), + }; - // TODO: Step 16: Nonce. + // TODO: Step 20: Nonce. - // Step 17: Integrity metadata. + // Step 21: Integrity metadata. let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity")); - let integrity_val = im_attribute.r().map(|a| a.value()); + let integrity_val = im_attribute.as_ref().map(|a| a.value()); let integrity_metadata = match integrity_val { Some(ref value) => &***value, None => "", }; - // TODO: Step 18: parser state. + // TODO: Step 22: referrer policy + + // Step 23 + let parser_metadata = if self.parser_inserted.get() { + ParserMetadata::ParserInserted + } else { + ParserMetadata::NotParserInserted + }; + + // Step 24. + let options = ScriptFetchOptions { + cryptographic_nonce: "".into(), + integrity_metadata: integrity_metadata.to_owned(), + parser_metadata, + referrer: self.global().get_referrer(), + referrer_policy: referrer_policy_for_element(self.upcast::<Element>()), + credentials_mode: module_credentials_mode, + }; - // TODO: Step 19: environment settings object. + // TODO: Step 23: environment settings object. let base_url = doc.base_url(); if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) { - // Step 20. + // Step 26. - // Step 20.1. + // Step 26.1. let src = src.value(); - // Step 20.2. + // Step 26.2. if src.is_empty() { self.queue_error_event(); return; } - // Step 20.3: The "from an external file"" flag is stored in ClassicScript. + // Step 26.3: The "from an external file"" flag is stored in ScriptOrigin. - // Step 20.4-20.5. + // Step 26.4-26.5. let url = match base_url.join(&src) { Ok(url) => url, Err(_) => { @@ -413,94 +752,233 @@ impl HTMLScriptElement { }, }; - // Preparation for step 22. - let kind = if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !async { - // Step 22.a: classic, has src, has defer, was parser-inserted, is not async. - ExternalScriptKind::Deferred - } else if was_parser_inserted && !async { - // Step 22.b: classic, has src, was parser-inserted, is not async. - ExternalScriptKind::ParsingBlocking - } else if !async && !self.non_blocking.get() { - // Step 22.c: classic, has src, is not async, is not non-blocking. - ExternalScriptKind::AsapInOrder - } else { - // Step 22.d: classic, has src. - ExternalScriptKind::Asap - }; - - // Step 20.6. - fetch_a_classic_script(self, kind, url, cors_setting, integrity_metadata.to_owned(), encoding); - - // Step 22. - 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), + // Step 26.6. + match script_type { + ScriptType::Classic => { + // Preparation for step 26. + let kind = if element.has_attribute(&local_name!("defer")) && + was_parser_inserted && + !asynch + { + // Step 26.a: classic, has src, has defer, was parser-inserted, is not async. + ExternalScriptKind::Deferred + } else if was_parser_inserted && !asynch { + // Step 26.c: classic, has src, was parser-inserted, is not async. + ExternalScriptKind::ParsingBlocking + } else if !asynch && !self.non_blocking.get() { + // Step 26.d: classic, has src, is not async, is not non-blocking. + ExternalScriptKind::AsapInOrder + } else { + // Step 26.f: classic, has src. + ExternalScriptKind::Asap + }; + + // Step 24.6. + fetch_a_classic_script(self, kind, url, cors_setting, options, encoding); + + // Step 23. + 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 => { + fetch_external_module_script( + ModuleOwner::Window(Trusted::new(self)), + url.clone(), + Destination::Script, + options, + ); + + 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); + }; + }, } } else { - // Step 21. + // Step 27. assert!(!text.is_empty()); - let result = Ok(ClassicScript::internal(text, base_url)); - - // Step 22. - if was_parser_inserted && - doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) && - doc.get_script_blocking_stylesheets_count() > 0 { - // Step 22.e: classic, has no src, was parser-inserted, is blocked on stylesheet. - doc.set_pending_parsing_blocking_script(self, Some(result)); - } else { - // Step 22.f: otherwise. - self.execute(result); + + let text_rc = Rc::new(text); + + // Step 27-1. & 27-2. + let result = Ok(ScriptOrigin::internal( + Rc::clone(&text_rc), + base_url.clone(), + options.clone(), + script_type.clone(), + )); + + // Step 27-2. + match script_type { + ScriptType::Classic => { + if was_parser_inserted && + doc.get_current_parser() + .map_or(false, |parser| parser.script_nesting_level() <= 1) && + doc.get_script_blocking_stylesheets_count() > 0 + { + // Step 27.h: classic, has no src, was parser-inserted, is blocked on stylesheet. + doc.set_pending_parsing_blocking_script(self, Some(result)); + } else { + // Step 27.i: otherwise. + self.execute(result); + } + }, + 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.clone(), + options, + ); + }, } } } - fn unminify_js(&self, script: &mut ClassicScript) { - if !opts::get().unminify_js { + fn unminify_js(&self, script: &mut ScriptOrigin) { + if !self.parser_document.window().unminify_js() { return; } - match Command::new("js-beautify") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() { - Err(_) => { - warn!("Failed to execute js-beautify. Will store unmodified script"); - }, - Ok(process) => { - let mut script_content = String::from(script.text.clone()); - let _ = process.stdin.unwrap().write_all(script_content.as_bytes()); - script_content.clear(); - let _ = process.stdout.unwrap().read_to_string(&mut script_content); + // Write the minified code to a temporary file and pass its path as an argument + // to js-beautify to read from. Meanwhile, redirect the process' stdout into + // another temporary file and read that into a string. This avoids some hangs + // observed on macOS when using direct input/output pipes with very large + // unminified content. + let (input, output) = (tempfile::NamedTempFile::new(), tempfile::tempfile()); + if let (Ok(mut input), Ok(mut output)) = (input, output) { + match &script.code { + SourceCode::Text(text) => { + input.write_all(text.as_bytes()).unwrap(); + }, + SourceCode::Compiled(compiled_source_code) => { + input + .write_all(compiled_source_code.original_text.as_bytes()) + .unwrap(); + }, + } + match Command::new("js-beautify") + .arg(input.path()) + .stdout(output.try_clone().unwrap()) + .status() + { + Ok(status) if status.success() => { + let mut script_content = String::new(); + output.seek(std::io::SeekFrom::Start(0)).unwrap(); + output.read_to_string(&mut script_content).unwrap(); + script.code = SourceCode::Text(Rc::new(DOMString::from(script_content))); + }, + _ => { + warn!("Failed to execute js-beautify. Will store unmodified script"); + }, + } + } else { + warn!("Error creating input and output files for unminify"); + } - script.text = DOMString::from(script_content); + let path; + match window_from_node(self).unminified_js_dir() { + Some(unminified_js_dir) => path = PathBuf::from(unminified_js_dir), + None => { + warn!("Unminified script directory not found"); + return; }, } - - let path = PathBuf::from(window_from_node(self).unminified_js_dir().unwrap()); - let path = if script.external { + let (base, has_name) = match script.url.as_str().ends_with("/") { + true => ( + path.join(&script.url[url::Position::BeforeHost..]) + .as_path() + .to_owned(), + false, + ), + false => ( + path.join(&script.url[url::Position::BeforeHost..]) + .parent() + .unwrap() + .to_owned(), + true, + ), + }; + match create_dir_all(base.clone()) { + Ok(()) => debug!("Created base dir: {:?}", base), + Err(e) => { + debug!("Failed to create base dir: {:?}, {:?}", base, e); + return; + }, + } + let path = if script.external && has_name { // External script. - let path_parts = script.url.path_segments().unwrap(); - match path_parts.last() { - Some(script_name) => path.join(script_name), - None => path.join(Uuid::new_v4().to_string()), - } + path.join(&script.url[url::Position::BeforeHost..]) } else { - // Inline script. - path.join(Uuid::new_v4().to_string()) + // Inline script or url ends with '/' + base.join(Uuid::new_v4().to_string()) }; debug!("script will be stored in {:?}", path); match File::create(&path) { - Ok(mut file) => file.write_all(script.text.as_bytes()).unwrap(), + Ok(mut file) => match &script.code { + SourceCode::Text(text) => file.write_all(text.as_bytes()).unwrap(), + SourceCode::Compiled(compiled_source_code) => { + file.write_all(compiled_source_code.original_text.as_bytes()) + .unwrap(); + }, + }, Err(why) => warn!("Could not store script {:?}", why), } } - /// https://html.spec.whatwg.org/multipage/#execute-the-script-block - pub fn execute(&self, result: Result<ClassicScript, NetworkError>) { + 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-block> + pub fn execute(&self, result: ScriptResult) { // Step 1. let doc = document_from_node(self); if self.parser_inserted.get() && &*doc != &*self.parser_document { @@ -513,15 +991,18 @@ impl HTMLScriptElement { warn!("error loading script {:?}", e); self.dispatch_error_event(); return; - } + }, Ok(script) => script, }; - self.unminify_js(&mut script); + if script.type_ == ScriptType::Classic { + self.unminify_js(&mut script); + self.substitute_with_local_script(&mut script); + } // Step 3. - let neutralized_doc = if script.external { + let neutralized_doc = if script.external || script.type_ == ScriptType::Module { debug!("loading external script, url = {}", script.url); let doc = document_from_node(self); doc.incr_ignore_destructive_writes_counter(); @@ -534,28 +1015,35 @@ impl HTMLScriptElement { let document = document_from_node(self); let old_script = document.GetCurrentScript(); - // Step 5.a.1. - document.set_current_script(Some(self)); - - // Step 5.a.2. - self.run_a_classic_script(&script); + match script.type_ { + ScriptType::Classic => document.set_current_script(Some(self)), + ScriptType::Module => document.set_current_script(None), + } - // Step 6. - document.set_current_script(old_script.r()); + match script.type_ { + ScriptType::Classic => { + self.run_a_classic_script(&script); + document.set_current_script(old_script.as_deref()); + }, + ScriptType::Module => { + assert!(document.GetCurrentScript().is_none()); + self.run_a_module_script(&script, false); + }, + } - // Step 7. + // Step 5. if let Some(doc) = neutralized_doc { doc.decr_ignore_destructive_writes_counter(); } - // Step 8. + // Step 6. if script.external { self.dispatch_load_event(); } } // https://html.spec.whatwg.org/multipage/#run-a-classic-script - pub fn run_a_classic_script(&self, script: &ClassicScript) { + pub fn run_a_classic_script(&self, script: &ScriptOrigin) { // TODO use a settings object rather than this element's document/window // Step 2 let document = document_from_node(self); @@ -565,82 +1053,177 @@ impl HTMLScriptElement { // Steps 4-10 let window = window_from_node(self); - let line_number = if script.external { 1 } else { self.line_number as u32 }; - rooted!(in(window.get_cx()) let mut rval = UndefinedValue()); + let line_number = if script.external { + 1 + } else { + self.line_number as u32 + }; + rooted!(in(*window.get_cx()) let mut rval = UndefinedValue()); let global = window.upcast::<GlobalScope>(); global.evaluate_script_on_global_with_result( - &script.text, script.url.as_str(), rval.handle_mut(), line_number); + &script.code, + script.url.as_str(), + rval.handle_mut(), + line_number, + script.fetch_options.clone(), + script.url.clone(), + ); + } + + #[allow(unsafe_code)] + /// https://html.spec.whatwg.org/multipage/#run-a-module-script + pub fn run_a_module_script(&self, script: &ScriptOrigin, _rethrow_errors: bool) { + // TODO use a settings object rather than this element's document/window + // Step 2 + let document = document_from_node(self); + if !document.is_fully_active() || !document.is_scripting_enabled() { + return; + } + + // Step 4 + let window = window_from_node(self); + let global = window.upcast::<GlobalScope>(); + 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); + return; + } + } + + let record = module_tree + .get_record() + .borrow() + .as_ref() + .map(|record| record.handle()); + + if let Some(record) = record { + let evaluated = module_tree.execute_module(global, record); + + if let Err(exception) = evaluated { + module_tree.set_rethrow_error(exception); + module_tree.report_error(&global); + return; + } + } + } } pub fn queue_error_event(&self) { let window = window_from_node(self); - window.dom_manipulation_task_source().queue_simple_event(self.upcast(), atom!("error"), &window); + window + .task_manager() + .dom_manipulation_task_source() + .queue_simple_event(self.upcast(), atom!("error"), &window); } pub fn dispatch_load_event(&self) { - self.dispatch_event(atom!("load"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable); + self.dispatch_event( + atom!("load"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); } pub fn dispatch_error_event(&self) { - self.dispatch_event(atom!("error"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable); + self.dispatch_event( + atom!("error"), + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + ); } - pub fn is_javascript(&self) -> bool { + // https://html.spec.whatwg.org/multipage/#prepare-a-script Step 7. + pub fn get_script_type(&self) -> Option<ScriptType> { let element = self.upcast::<Element>(); + let type_attr = element.get_attribute(&ns!(), &local_name!("type")); - let is_js = match type_attr.as_ref().map(|s| s.value()) { - Some(ref s) if s.is_empty() => { - // type attr exists, but empty means js + let language_attr = element.get_attribute(&ns!(), &local_name!("language")); + + let script_type = 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"); - true + Some(ScriptType::Classic) }, - Some(s) => { - debug!("script type={}", &**s); - SCRIPT_JS_MIMES.contains(&s.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS)) + (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 == String::from("module") { + return Some(ScriptType::Module); + } + + if SCRIPT_JS_MIMES + .contains(&ty.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS)) + { + Some(ScriptType::Classic) + } else { + None + } }, - None => { - debug!("no script type"); - let language_attr = element.get_attribute(&ns!(), &local_name!("language")); - let is_js = match language_attr.as_ref().map(|s| s.value()) { - Some(ref s) if s.is_empty() => { - debug!("script language empty, inferring js"); - true - }, - Some(s) => { - debug!("script language={}", &**s); - let mut language = format!("text/{}", &**s); - language.make_ascii_lowercase(); - SCRIPT_JS_MIMES.contains(&&*language) - }, - None => { - debug!("no script type or language, inferring js"); - true - } - }; - // https://github.com/rust-lang/rust/issues/21114 - is_js - } }; + // https://github.com/rust-lang/rust/issues/21114 - is_js + script_type } pub fn set_parser_inserted(&self, parser_inserted: bool) { self.parser_inserted.set(parser_inserted); } + pub fn get_parser_inserted(&self) -> bool { + self.parser_inserted.get() + } + pub fn set_already_started(&self, already_started: bool) { self.already_started.set(already_started); } - fn dispatch_event(&self, - type_: Atom, - bubbles: EventBubbles, - cancelable: EventCancelable) -> EventStatus { + pub fn get_non_blocking(&self) -> bool { + self.non_blocking.get() + } + + fn dispatch_event( + &self, + type_: Atom, + bubbles: EventBubbles, + cancelable: EventCancelable, + ) -> EventStatus { let window = window_from_node(self); let event = Event::new(window.upcast(), type_, bubbles, cancelable); event.fire(self.upcast()) @@ -648,8 +1231,8 @@ impl HTMLScriptElement { } impl VirtualMethods for HTMLScriptElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { @@ -657,7 +1240,7 @@ impl VirtualMethods for HTMLScriptElement { match *attr.local_name() { local_name!("src") => { if let AttributeMutation::Set(_) = mutation { - if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() { + if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { self.prepare(); } } @@ -670,30 +1253,39 @@ impl VirtualMethods for HTMLScriptElement { if let Some(ref s) = self.super_type() { s.children_changed(mutation); } - if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() { + if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { self.prepare(); } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } - if tree_in_doc && !self.parser_inserted.get() { - self.prepare(); + if context.tree_connected && !self.parser_inserted.get() { + let script = Trusted::new(self); + document_from_node(self).add_delayed_task(task!(ScriptDelayedInitialize: move || { + script.root().prepare(); + })); } } - fn cloning_steps(&self, copy: &Node, maybe_doc: Option<&Document>, - clone_children: CloneChildrenFlag) { + fn cloning_steps( + &self, + copy: &Node, + maybe_doc: Option<&Document>, + clone_children: CloneChildrenFlag, + ) { if let Some(ref s) = self.super_type() { s.cloning_steps(copy, maybe_doc, clone_children); } // https://html.spec.whatwg.org/multipage/#already-started if self.already_started.get() { - copy.downcast::<HTMLScriptElement>().unwrap().set_already_started(true); + copy.downcast::<HTMLScriptElement>() + .unwrap() + .set_already_started(true); } } } @@ -701,8 +1293,9 @@ impl VirtualMethods for HTMLScriptElement { impl HTMLScriptElementMethods for HTMLScriptElement { // https://html.spec.whatwg.org/multipage/#dom-script-src make_url_getter!(Src, "src"); + // https://html.spec.whatwg.org/multipage/#dom-script-src - make_setter!(SetSrc, "src"); + make_url_setter!(SetSrc, "src"); // https://html.spec.whatwg.org/multipage/#dom-script-type make_getter!(Type, "type"); @@ -716,13 +1309,16 @@ impl HTMLScriptElementMethods for HTMLScriptElement { // 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")) + 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) { self.non_blocking.set(false); - self.upcast::<Element>().set_bool_attribute(&local_name!("async"), value); + self.upcast::<Element>() + .set_bool_attribute(&local_name!("async"), value); } // https://html.spec.whatwg.org/multipage/#dom-script-defer @@ -730,6 +1326,11 @@ impl HTMLScriptElementMethods for HTMLScriptElement { // 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 @@ -755,6 +1356,14 @@ impl HTMLScriptElementMethods for HTMLScriptElement { set_cross_origin_attribute(self.upcast::<Element>(), value); } + // 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://html.spec.whatwg.org/multipage/#dom-script-text fn Text(&self) -> DOMString { self.upcast::<Node>().child_text_content() |