mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Support persisting unminified external stylesheets (#33919)
* Support local tweaking of external stylesheets Signed-off-by: Taym <haddadi.taym@gmail.com> * Remove duplicated code between unminify_css and unminify_js Signed-off-by: Taym <haddadi.taym@gmail.com> * Add License Signed-off-by: Taym <haddadi.taym@gmail.com> * Use js-beautify instead of npx Signed-off-by: Taym <haddadi.taym@gmail.com> * Fix clippy warning Signed-off-by: Taym <haddadi.taym@gmail.com> --------- Signed-off-by: Taym <haddadi.taym@gmail.com>
This commit is contained in:
parent
bac1101163
commit
ee68dc2589
8 changed files with 206 additions and 77 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -57,6 +57,7 @@ Session.vim
|
|||
Sessionx.vim
|
||||
|
||||
/unminified-js
|
||||
/unminified-css
|
||||
|
||||
# Layout debugger trace files
|
||||
layout_trace*
|
||||
|
|
|
@ -126,6 +126,9 @@ pub struct Opts {
|
|||
/// Directory path that was created with "unminify-js"
|
||||
pub local_script_source: Option<String>,
|
||||
|
||||
/// 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"),
|
||||
};
|
||||
|
||||
|
|
|
@ -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() => {
|
||||
|
||||
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)));
|
||||
},
|
||||
_ => {
|
||||
warn!("Failed to execute js-beautify. Will store unmodified script");
|
||||
},
|
||||
}
|
||||
} 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) => {
|
||||
|
|
|
@ -297,6 +297,10 @@ pub struct Window {
|
|||
/// opt is enabled.
|
||||
unminified_js_dir: DomRefCell<Option<String>>,
|
||||
|
||||
/// Directory to store unminified css for this window if unminify-css
|
||||
/// opt is enabled.
|
||||
unminified_css_dir: DomRefCell<Option<String>>,
|
||||
|
||||
/// Directory with stored unminified scripts
|
||||
local_script_source: Option<String>,
|
||||
|
||||
|
@ -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<String> {
|
||||
self.unminified_css_dir.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn local_script_source(&self) -> &Option<String> {
|
||||
&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<String>,
|
||||
userscripts_path: Option<String>,
|
||||
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<Option<String>>, 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,8 @@ mod webdriver_handlers;
|
|||
#[warn(deprecated)]
|
||||
mod window_named_properties;
|
||||
|
||||
mod unminify;
|
||||
|
||||
mod links;
|
||||
|
||||
pub use init::init;
|
||||
|
|
|
@ -707,6 +707,9 @@ pub struct ScriptThread {
|
|||
/// Directory with stored unminified scripts
|
||||
local_script_source: Option<String>,
|
||||
|
||||
/// 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,
|
||||
|
|
|
@ -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<u8>, file_url: ServoUrl) -> Vec<u8> {
|
||||
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![]
|
||||
};
|
||||
|
|
100
components/script/unminify.rs
Normal file
100
components/script/unminify.rs
Normal file
|
@ -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<String>,
|
||||
url: &ServoUrl,
|
||||
external: Option<bool>,
|
||||
) -> Result<File, Error> {
|
||||
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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue