forked from mirrors/gecko-dev
		
	 043b853e17
			
		
	
	
		043b853e17
		
	
	
	
	
		
			
			Source-Repo: https://github.com/servo/servo Source-Revision: d1bc1a4f1b66ab9f63fa37f649eaf79035e12f8e
		
			
				
	
	
		
			749 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			749 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| /* 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::AttrBinding::AttrMethods;
 | |
| 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::event::{Event, EventBubbles, EventCancelable};
 | |
| use dom::eventdispatcher::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;
 | |
| use encoding::label::encoding_from_whatwg_label;
 | |
| use encoding::types::{DecoderTrap, EncodingRef};
 | |
| use html5ever_atoms::LocalName;
 | |
| use ipc_channel::ipc;
 | |
| use ipc_channel::router::ROUTER;
 | |
| 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 servo_atoms::Atom;
 | |
| use servo_url::ServoUrl;
 | |
| use std::ascii::AsciiExt;
 | |
| use std::cell::Cell;
 | |
| use std::sync::{Arc, Mutex};
 | |
| use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
 | |
| 
 | |
| #[dom_struct]
 | |
| pub 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>,
 | |
| 
 | |
|     /// https://html.spec.whatwg.org/multipage/#ready-to-be-parser-executed
 | |
|     ready_to_be_parser_executed: Cell<bool>,
 | |
| 
 | |
|     /// Document of the parser that created this element
 | |
|     parser_document: JS<Document>,
 | |
| }
 | |
| 
 | |
| impl HTMLScriptElement {
 | |
|     fn new_inherited(local_name: LocalName, prefix: Option<DOMString>, document: &Document,
 | |
|                      creator: ElementCreator) -> HTMLScriptElement {
 | |
|         HTMLScriptElement {
 | |
|             htmlelement:
 | |
|                 HTMLElement::new_inherited(local_name, prefix, document),
 | |
|             already_started: Cell::new(false),
 | |
|             parser_inserted: Cell::new(creator == ElementCreator::ParserCreated),
 | |
|             non_blocking: Cell::new(creator != ElementCreator::ParserCreated),
 | |
|             ready_to_be_parser_executed: Cell::new(false),
 | |
|             parser_document: JS::from_ref(document),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[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)
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /// Supported script types as defined by
 | |
| /// <https://html.spec.whatwg.org/multipage/#javascript-mime-type>.
 | |
| 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(HeapSizeOf, JSTraceable)]
 | |
| pub struct ClassicScript {
 | |
|     text: DOMString,
 | |
|     url: ServoUrl,
 | |
|     external: bool,
 | |
| }
 | |
| 
 | |
| impl ClassicScript {
 | |
|     fn internal(text: DOMString, url: ServoUrl) -> ClassicScript {
 | |
|         ClassicScript {
 | |
|             text: text,
 | |
|             url: url,
 | |
|             external: false,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn external(text: DOMString, url: ServoUrl) -> ClassicScript {
 | |
|         ClassicScript {
 | |
|             text: text,
 | |
|             url: url,
 | |
|             external: true,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub type ScriptResult = Result<ClassicScript, NetworkError>;
 | |
| 
 | |
| /// The context required for asynchronously loading an external script source.
 | |
| struct ScriptContext {
 | |
|     /// 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,
 | |
|     /// 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>
 | |
| }
 | |
| 
 | |
| impl FetchResponseListener for ScriptContext {
 | |
|     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>) {
 | |
|         self.metadata = metadata.ok().map(|meta| match meta {
 | |
|             FetchMetadata::Unfiltered(m) => m,
 | |
|             FetchMetadata::Filtered { unsafe_, .. } => unsafe_
 | |
|         });
 | |
| 
 | |
|         let status_code = self.metadata.as_ref().and_then(|m| {
 | |
|             match m.status {
 | |
|                 Some((c, _)) => Some(c),
 | |
|                 _ => None,
 | |
|             }
 | |
|         }).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)))
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     fn process_response_chunk(&mut self, 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
 | |
|     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();
 | |
| 
 | |
|             // Step 6.
 | |
|             let encoding = metadata.charset
 | |
|                 .and_then(|encoding| encoding_from_whatwg_label(&encoding))
 | |
|                 .unwrap_or(self.character_encoding);
 | |
| 
 | |
|             // Step 7.
 | |
|             let source_text = encoding.decode(&self.data, DecoderTrap::Replace).unwrap();
 | |
|             ClassicScript::external(DOMString::from(source_text), metadata.final_url)
 | |
|         });
 | |
| 
 | |
|         // 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();
 | |
|         elem.ready_to_be_parser_executed.set(true);
 | |
|         let document = document_from_node(&*elem);
 | |
| 
 | |
|         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),
 | |
|         }
 | |
| 
 | |
|         document.finish_load(LoadType::Script(self.url.clone()));
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PreInvoke for ScriptContext {}
 | |
| 
 | |
| /// 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) {
 | |
|     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()
 | |
|     };
 | |
| 
 | |
|     // TODO: Step 3, Add custom steps to perform fetch
 | |
| 
 | |
|     let context = Arc::new(Mutex::new(ScriptContext {
 | |
|         elem: Trusted::new(script),
 | |
|         kind: kind,
 | |
|         character_encoding: character_encoding,
 | |
|         data: vec!(),
 | |
|         metadata: None,
 | |
|         url: url.clone(),
 | |
|         status: Ok(())
 | |
|     }));
 | |
| 
 | |
|     let (action_sender, action_receiver) = ipc::channel().unwrap();
 | |
|     let listener = NetworkListener {
 | |
|         context: context,
 | |
|         task_source: doc.window().networking_task_source(),
 | |
|         wrapper: Some(doc.window().get_runnable_wrapper())
 | |
|     };
 | |
| 
 | |
|     ROUTER.add_route(action_receiver.to_opaque(), box 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
 | |
|     pub fn prepare(&self) {
 | |
|         // Step 1.
 | |
|         if self.already_started.get() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Step 2.
 | |
|         let was_parser_inserted = self.parser_inserted.get();
 | |
|         self.parser_inserted.set(false);
 | |
| 
 | |
|         // Step 3.
 | |
|         let element = self.upcast::<Element>();
 | |
|         let async = 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 {
 | |
|             self.non_blocking.set(true);
 | |
|         }
 | |
| 
 | |
|         // Step 4.
 | |
|         let text = self.Text();
 | |
|         if text.is_empty() && !element.has_attribute(&local_name!("src")) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Step 5.
 | |
|         if !self.upcast::<Node>().is_in_doc() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Step 6.
 | |
|         if !self.is_javascript() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Step 7.
 | |
|         if was_parser_inserted {
 | |
|             self.parser_inserted.set(true);
 | |
|             self.non_blocking.set(false);
 | |
|         }
 | |
| 
 | |
|         // Step 8.
 | |
|         self.already_started.set(true);
 | |
| 
 | |
|         // Step 9.
 | |
|         let doc = document_from_node(self);
 | |
|         if self.parser_inserted.get() && &*self.parser_document != &*doc {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Step 10.
 | |
|         if !doc.is_scripting_enabled() {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // TODO(#4577): Step 11: CSP.
 | |
| 
 | |
|         // 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;
 | |
|                 }
 | |
| 
 | |
|                 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 14.
 | |
|         let cors_setting = match self.GetCrossOrigin() {
 | |
|             Some(ref s) if *s == "anonymous" => Some(CorsSettings::Anonymous),
 | |
|             Some(ref s) if *s == "use-credentials" => Some(CorsSettings::UseCredentials),
 | |
|             None => None,
 | |
|             _ => unreachable!()
 | |
|         };
 | |
| 
 | |
|         // TODO: Step 15: Module script credentials mode.
 | |
| 
 | |
|         // TODO: Step 16: Nonce.
 | |
| 
 | |
|         // Step 17: Integrity metadata.
 | |
|         let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
 | |
|         let integrity_val = im_attribute.r().map(|a| a.value());
 | |
|         let integrity_metadata = match integrity_val {
 | |
|             Some(ref value) => &***value,
 | |
|             None => "",
 | |
|         };
 | |
| 
 | |
|         // TODO: Step 18: parser state.
 | |
| 
 | |
|         // TODO: Step 19: environment settings object.
 | |
| 
 | |
|         let base_url = doc.base_url();
 | |
|         if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
 | |
|             // Step 20.
 | |
| 
 | |
|             // Step 20.1.
 | |
|             let src = src.value();
 | |
| 
 | |
|             // Step 20.2.
 | |
|             if src.is_empty() {
 | |
|                 self.queue_error_event();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Step 20.3: The "from an external file"" flag is stored in ClassicScript.
 | |
| 
 | |
|             // Step 20.4-20.5.
 | |
|             let url = match base_url.join(&src) {
 | |
|                 Ok(url) => url,
 | |
|                 Err(_) => {
 | |
|                     warn!("error parsing URL for script {}", &**src);
 | |
|                     self.queue_error_event();
 | |
|                     return;
 | |
|                 },
 | |
|             };
 | |
| 
 | |
|             // 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),
 | |
|             }
 | |
|         } else {
 | |
|             // Step 21.
 | |
|             assert!(!text.is_empty());
 | |
|             let result = Ok(ClassicScript::internal(text, base_url));
 | |
|             self.ready_to_be_parser_executed.set(true);
 | |
| 
 | |
|             // 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);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn is_ready_to_be_executed(&self) -> bool {
 | |
|         self.ready_to_be_parser_executed.get()
 | |
|     }
 | |
| 
 | |
|     /// https://html.spec.whatwg.org/multipage/#execute-the-script-block
 | |
|     pub fn execute(&self, result: Result<ClassicScript, NetworkError>) {
 | |
|         assert!(self.ready_to_be_parser_executed.get());
 | |
| 
 | |
|         // Step 1.
 | |
|         let doc = document_from_node(self);
 | |
|         if self.parser_inserted.get() && &*doc != &*self.parser_document {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let script = match result {
 | |
|             // Step 2.
 | |
|             Err(e) => {
 | |
|                 warn!("error loading script {:?}", e);
 | |
|                 self.dispatch_error_event();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             Ok(script) => script,
 | |
|         };
 | |
| 
 | |
|         // TODO(#12446): beforescriptexecute.
 | |
|         if self.dispatch_before_script_execute_event() == EventStatus::Canceled {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Step 3.
 | |
|         let neutralized_doc = if script.external {
 | |
|             debug!("loading external script, url = {}", script.url);
 | |
|             let doc = document_from_node(self);
 | |
|             doc.incr_ignore_destructive_writes_counter();
 | |
|             Some(doc)
 | |
|         } else {
 | |
|             None
 | |
|         };
 | |
| 
 | |
|         // Step 4.
 | |
|         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.
 | |
|         let window = window_from_node(self);
 | |
|         rooted!(in(window.get_cx()) let mut rval = UndefinedValue());
 | |
|         window.upcast::<GlobalScope>().evaluate_script_on_global_with_result(
 | |
|             &script.text, script.url.as_str(), rval.handle_mut());
 | |
| 
 | |
|         // Step 6.
 | |
|         document.set_current_script(old_script.r());
 | |
| 
 | |
|         // Step 7.
 | |
|         if let Some(doc) = neutralized_doc {
 | |
|             doc.decr_ignore_destructive_writes_counter();
 | |
|         }
 | |
| 
 | |
|         // TODO(#12446): afterscriptexecute.
 | |
|         self.dispatch_after_script_execute_event();
 | |
| 
 | |
|         // Step 8.
 | |
|         if script.external {
 | |
|             self.dispatch_load_event();
 | |
|         } else {
 | |
|             window.dom_manipulation_task_source().queue_simple_event(self.upcast(), atom!("load"), &window);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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);
 | |
|     }
 | |
| 
 | |
|     pub fn dispatch_before_script_execute_event(&self) -> EventStatus {
 | |
|         self.dispatch_event(atom!("beforescriptexecute"),
 | |
|                             EventBubbles::Bubbles,
 | |
|                             EventCancelable::Cancelable)
 | |
|     }
 | |
| 
 | |
|     pub fn dispatch_after_script_execute_event(&self) {
 | |
|         self.dispatch_event(atom!("afterscriptexecute"),
 | |
|                             EventBubbles::Bubbles,
 | |
|                             EventCancelable::NotCancelable);
 | |
|     }
 | |
| 
 | |
|     pub fn dispatch_load_event(&self) {
 | |
|         self.dispatch_event(atom!("load"),
 | |
|                             EventBubbles::DoesNotBubble,
 | |
|                             EventCancelable::NotCancelable);
 | |
|     }
 | |
| 
 | |
|     pub fn dispatch_error_event(&self) {
 | |
|         self.dispatch_event(atom!("error"),
 | |
|                             EventBubbles::DoesNotBubble,
 | |
|                             EventCancelable::NotCancelable);
 | |
|     }
 | |
| 
 | |
|     pub fn is_javascript(&self) -> bool {
 | |
|         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
 | |
|                 debug!("script type empty, inferring js");
 | |
|                 true
 | |
|             },
 | |
|             Some(s) => {
 | |
|                 debug!("script type={}", &**s);
 | |
|                 SCRIPT_JS_MIMES.contains(&s.to_ascii_lowercase().trim_matches(HTML_SPACE_CHARACTERS))
 | |
|             },
 | |
|             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
 | |
|     }
 | |
| 
 | |
|     pub fn set_parser_inserted(&self, parser_inserted: bool) {
 | |
|         self.parser_inserted.set(parser_inserted);
 | |
|     }
 | |
| 
 | |
|     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 {
 | |
|         let window = window_from_node(self);
 | |
|         let event = Event::new(window.upcast(), type_, bubbles, cancelable);
 | |
|         event.fire(self.upcast())
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl VirtualMethods for HTMLScriptElement {
 | |
|     fn super_type(&self) -> Option<&VirtualMethods> {
 | |
|         Some(self.upcast::<HTMLElement>() as &VirtualMethods)
 | |
|     }
 | |
| 
 | |
|     fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
 | |
|         self.super_type().unwrap().attribute_mutated(attr, mutation);
 | |
|         match *attr.local_name() {
 | |
|             local_name!("src") => {
 | |
|                 if let AttributeMutation::Set(_) = mutation {
 | |
|                     if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() {
 | |
|                         self.prepare();
 | |
|                     }
 | |
|                 }
 | |
|             },
 | |
|             _ => {},
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn children_changed(&self, mutation: &ChildrenMutation) {
 | |
|         if let Some(ref s) = self.super_type() {
 | |
|             s.children_changed(mutation);
 | |
|         }
 | |
|         if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() {
 | |
|             self.prepare();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn bind_to_tree(&self, tree_in_doc: bool) {
 | |
|         if let Some(ref s) = self.super_type() {
 | |
|             s.bind_to_tree(tree_in_doc);
 | |
|         }
 | |
| 
 | |
|         if tree_in_doc && !self.parser_inserted.get() {
 | |
|             self.prepare();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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");
 | |
| 
 | |
|     // 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-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-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> {
 | |
|         let element = self.upcast::<Element>();
 | |
|         let attr = element.get_attribute(&ns!(), &local_name!("crossorigin"));
 | |
| 
 | |
|         if let Some(mut val) = attr.map(|v| v.Value()) {
 | |
|             val.make_ascii_lowercase();
 | |
|             if val == "anonymous" || val == "use-credentials" {
 | |
|                 return Some(val);
 | |
|             }
 | |
|             return Some(DOMString::from("anonymous"));
 | |
|         }
 | |
|         None
 | |
|     }
 | |
| 
 | |
|     // https://html.spec.whatwg.org/multipage/#dom-script-crossorigin
 | |
|     fn SetCrossOrigin(&self, value: Option<DOMString>) {
 | |
|         let element = self.upcast::<Element>();
 | |
|         match value {
 | |
|             Some(val) => element.set_string_attribute(&local_name!("crossorigin"), val),
 | |
|             None => {
 | |
|                 element.remove_attribute(&ns!(), &local_name!("crossorigin"));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // https://html.spec.whatwg.org/multipage/#dom-script-text
 | |
|     fn Text(&self) -> DOMString {
 | |
|         self.upcast::<Node>().child_text_content()
 | |
|     }
 | |
| 
 | |
|     // https://html.spec.whatwg.org/multipage/#dom-script-text
 | |
|     fn SetText(&self, value: DOMString) {
 | |
|         self.upcast::<Node>().SetTextContent(Some(value))
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Clone, Copy)]
 | |
| enum ExternalScriptKind {
 | |
|     Deferred,
 | |
|     ParsingBlocking,
 | |
|     AsapInOrder,
 | |
|     Asap,
 | |
| }
 |