diff --git a/.gitignore b/.gitignore index 19738f1b45d..8d0893d932c 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ Session.vim Sessionx.vim /unminified-js +/unminified-css # Layout debugger trace files layout_trace* diff --git a/components/config/opts.rs b/components/config/opts.rs index 0012ad103e3..62645c294b2 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -126,6 +126,9 @@ pub struct Opts { /// Directory path that was created with "unminify-js" pub local_script_source: Option, + /// Unminify Css. + pub unminify_css: bool, + /// Print Progressive Web Metrics to console. pub print_pwm: bool, } @@ -421,6 +424,7 @@ pub fn default_opts() -> Opts { ignore_certificate_errors: false, unminify_js: false, local_script_source: None, + unminify_css: false, print_pwm: false, } } @@ -560,6 +564,7 @@ pub fn from_cmdline_args(mut opts: Options, args: &[String]) -> ArgumentParsingR "Directory root with unminified scripts", "", ); + opts.optflag("", "unminify-css", "Unminify Css"); let opt_match = match opts.parse(args) { Ok(m) => m, @@ -761,6 +766,7 @@ pub fn from_cmdline_args(mut opts: Options, args: &[String]) -> ArgumentParsingR ignore_certificate_errors: opt_match.opt_present("ignore-certificate-errors"), unminify_js: opt_match.opt_present("unminify-js"), local_script_source: opt_match.opt_str("local-script-source"), + unminify_css: opt_match.opt_present("unminify-css"), print_pwm: opt_match.opt_present("print-pwm"), }; diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 1f2ddafb2e0..34239b0d836 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -70,6 +70,9 @@ use crate::script_runtime::CanGc; use crate::task::TaskCanceller; use crate::task_source::dom_manipulation::DOMManipulationTaskSource; use crate::task_source::{TaskSource, TaskSourceName}; +use crate::unminify::{ + create_output_file, create_temp_files, execute_js_beautify, BeautifyFileType, +}; // TODO Implement offthread compilation in mozjs /*pub struct OffThreadCompilationContext { @@ -853,17 +856,11 @@ impl HTMLScriptElement { } fn unminify_js(&self, script: &mut ScriptOrigin) { - if !self.parser_document.window().unminify_js() { + if self.parser_document.window().unminified_js_dir().is_none() { return; } - // 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) { + if let Some((mut input, mut output)) = create_temp_files() { match &script.code { SourceCode::Text(text) => { input.write_all(text.as_bytes()).unwrap(); @@ -874,66 +871,24 @@ impl HTMLScriptElement { .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"); - }, + + if execute_js_beautify( + input.path(), + output.try_clone().unwrap(), + BeautifyFileType::Js, + ) { + 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))); } - } else { - warn!("Error creating input and output files for unminify"); } - let path = match window_from_node(self).unminified_js_dir() { - Some(unminified_js_dir) => PathBuf::from(unminified_js_dir), - None => { - warn!("Unminified script directory not found"); - return; - }, - }; - - 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. - path.join(&script.url[url::Position::BeforeHost..]) - } else { - // Inline script or url ends with '/' - base.join(Uuid::new_v4().to_string()) - }; - - debug!("script will be stored in {:?}", path); - - match File::create(&path) { + match create_output_file( + window_from_node(self).unminified_js_dir(), + &script.url, + Some(script.external), + ) { Ok(mut file) => match &script.code { SourceCode::Text(text) => file.write_all(text.as_bytes()).unwrap(), SourceCode::Compiled(compiled_source_code) => { diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index efdf33f1dd5..f6ca5d96861 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -297,6 +297,10 @@ pub struct Window { /// opt is enabled. unminified_js_dir: DomRefCell>, + /// Directory to store unminified css for this window if unminify-css + /// opt is enabled. + unminified_css_dir: DomRefCell>, + /// Directory with stored unminified scripts local_script_source: Option, @@ -330,6 +334,9 @@ pub struct Window { /// Unminify Javascript. unminify_js: bool, + /// Unminify Css. + unminify_css: bool, + /// Where to load userscripts from, if any. An empty string will load from /// the resources/user-agent-js directory, and if the option isn't passed userscripts /// won't be loaded. @@ -538,10 +545,6 @@ impl Window { self.replace_surrogates } - pub fn unminify_js(&self) -> bool { - self.unminify_js - } - pub fn get_player_context(&self) -> WindowGLContext { self.player_context.clone() } @@ -2248,13 +2251,14 @@ impl Window { assert!(self.document.get().is_none()); assert!(document.window() == self); self.document.set(Some(document)); - if !self.unminify_js { - return; - } - // Set a path for the document host to store unminified scripts. - let mut path = env::current_dir().unwrap(); - path.push("unminified-js"); - *self.unminified_js_dir.borrow_mut() = Some(path.into_os_string().into_string().unwrap()); + + set_unminified_path(self.unminify_js, &self.unminified_js_dir, "unminified-js"); + + set_unminified_path( + self.unminify_css, + &self.unminified_css_dir, + "unminified-css", + ); } /// Commence a new URL load which will either replace this window or scroll to a fragment. @@ -2521,6 +2525,10 @@ impl Window { self.unminified_js_dir.borrow().clone() } + pub fn unminified_css_dir(&self) -> Option { + self.unminified_css_dir.borrow().clone() + } + pub fn local_script_source(&self) -> &Option { &self.local_script_source } @@ -2585,6 +2593,7 @@ impl Window { relayout_event: bool, prepare_for_screenshot: bool, unminify_js: bool, + unminify_css: bool, local_script_source: Option, userscripts_path: Option, is_headless: bool, @@ -2661,6 +2670,7 @@ impl Window { webxr_registry, pending_layout_images: Default::default(), unminified_js_dir: Default::default(), + unminified_css_dir: Default::default(), local_script_source, test_worklet: Default::default(), paint_worklet: Default::default(), @@ -2671,6 +2681,7 @@ impl Window { relayout_event, prepare_for_screenshot, unminify_js, + unminify_css, userscripts_path, replace_surrogates, player_context, @@ -2902,3 +2913,12 @@ fn is_named_element_with_name_attribute(elem: &Element) -> bool { fn is_named_element_with_id_attribute(elem: &Element) -> bool { elem.is_html_element() } + +fn set_unminified_path(option: bool, dir_ref: &DomRefCell>, folder_name: &str) { + if option { + // Set a path for the document host to store unminified files. + let mut path = env::current_dir().unwrap(); + path.push(folder_name); + *dir_ref.borrow_mut() = Some(path.into_os_string().into_string().unwrap()); + } +} diff --git a/components/script/lib.rs b/components/script/lib.rs index ca70f062e64..043f3fe2b4e 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -94,6 +94,8 @@ mod webdriver_handlers; #[warn(deprecated)] mod window_named_properties; +mod unminify; + mod links; pub use init::init; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 0ea018f40ef..29fb9807ce4 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -707,6 +707,9 @@ pub struct ScriptThread { /// Directory with stored unminified scripts local_script_source: Option, + /// Unminify Css. + unminify_css: bool, + /// Where to load userscripts from, if any. An empty string will load from /// the resources/user-agent-js directory, and if the option isn't passed userscripts /// won't be loaded @@ -1343,6 +1346,7 @@ impl ScriptThread { prepare_for_screenshot, unminify_js: opts.unminify_js, local_script_source: opts.local_script_source.clone(), + unminify_css: opts.unminify_css, userscripts_path: opts.userscripts.clone(), headless: opts.headless, @@ -3672,6 +3676,7 @@ impl ScriptThread { self.relayout_event, self.prepare_for_screenshot, self.unminify_js, + self.unminify_css, self.local_script_source.clone(), self.userscripts_path.clone(), self.headless, diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index fa48dffe6f8..d076d1e7f06 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -2,6 +2,7 @@ * 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::io::{Read, Seek, Write}; use std::sync::atomic::AtomicBool; use base::id::PipelineId; @@ -42,6 +43,9 @@ use crate::dom::shadowroot::ShadowRoot; use crate::fetch::create_a_potential_cors_request; use crate::network_listener::{self, PreInvoke, ResourceTimingListener}; use crate::script_runtime::CanGc; +use crate::unminify::{ + create_output_file, create_temp_files, execute_js_beautify, BeautifyFileType, +}; pub trait StylesheetOwner { /// Returns whether this element was inserted by the parser (i.e., it should @@ -88,6 +92,41 @@ pub struct StylesheetContext { resource_timing: ResourceFetchTiming, } +impl StylesheetContext { + fn unminify_css(&self, data: Vec, file_url: ServoUrl) -> Vec { + if self.document.root().window().unminified_css_dir().is_none() { + return data; + } + + let mut style_content = data; + + if let Some((input, mut output)) = create_temp_files() { + if execute_js_beautify( + input.path(), + output.try_clone().unwrap(), + BeautifyFileType::Css, + ) { + output.seek(std::io::SeekFrom::Start(0)).unwrap(); + output.read_to_end(&mut style_content).unwrap(); + } + } + match create_output_file( + self.document.root().window().unminified_css_dir(), + &file_url, + None, + ) { + Ok(mut file) => { + file.write_all(&style_content).unwrap(); + }, + Err(why) => { + log::warn!("Could not store script {:?}", why); + }, + } + + style_content + } +} + impl PreInvoke for StylesheetContext {} impl FetchResponseListener for StylesheetContext { @@ -134,7 +173,8 @@ impl FetchResponseListener for StylesheetContext { }); let data = if is_css { - std::mem::take(&mut self.data) + let data = std::mem::take(&mut self.data); + self.unminify_css(data, metadata.final_url.clone()) } else { vec![] }; diff --git a/components/script/unminify.rs b/components/script/unminify.rs new file mode 100644 index 00000000000..5292e68fedb --- /dev/null +++ b/components/script/unminify.rs @@ -0,0 +1,100 @@ +/* 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::fs::{create_dir_all, File}; +use std::io::{Error, ErrorKind}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use servo_url::ServoUrl; +use tempfile::NamedTempFile; +use uuid::Uuid; + +pub fn create_temp_files() -> Option<(NamedTempFile, File)> { + // 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) = (NamedTempFile::new(), tempfile::tempfile()); + if let (Ok(input), Ok(output)) = (input, output) { + Some((input, output)) + } else { + log::warn!("Error creating input and output temp files"); + None + } +} + +#[derive(Debug)] +pub enum BeautifyFileType { + Css, + Js, +} + +pub fn execute_js_beautify(input: &Path, output: File, file_type: BeautifyFileType) -> bool { + let mut cmd = Command::new("js-beautify"); + match file_type { + BeautifyFileType::Js => (), + BeautifyFileType::Css => { + cmd.arg("--type").arg("css"); + }, + } + match cmd.arg(input).stdout(output).status() { + Ok(status) => status.success(), + _ => { + log::warn!( + "Failed to execute js-beautify --type {:?}, Will store unmodified script", + file_type + ); + false + }, + } +} + +pub fn create_output_file( + unminified_dir: Option, + url: &ServoUrl, + external: Option, +) -> Result { + let path = match unminified_dir { + Some(unminified_dir) => PathBuf::from(unminified_dir), + None => { + warn!("Unminified file directory not found"); + return Err(Error::new( + ErrorKind::NotFound, + "Unminified file directory not found", + )); + }, + }; + + let (base, has_name) = match url.as_str().ends_with('/') { + true => ( + path.join(&url[url::Position::BeforeHost..]) + .as_path() + .to_owned(), + false, + ), + false => ( + path.join(&url[url::Position::BeforeHost..]) + .parent() + .unwrap() + .to_owned(), + true, + ), + }; + + create_dir_all(&base)?; + + let path = if external.unwrap_or(true) && has_name { + // External. + path.join(&url[url::Position::BeforeHost..]) + } else { + // Inline file or url ends with '/' + base.join(Uuid::new_v4().to_string()) + }; + + debug!("Unminified files will be stored in {:?}", path); + + File::create(path) +}