1use std::env;
6use std::fs::{File, create_dir_all};
7use std::io::{Error, Read, Seek, Write};
8use std::path::{Path, PathBuf};
9use std::process::Command;
10use std::rc::Rc;
11
12use servo_url::ServoUrl;
13use tempfile::NamedTempFile;
14use uuid::Uuid;
15
16use crate::dom::bindings::str::DOMString;
17
18pub(crate) trait ScriptSource {
19 fn unminified_dir(&self) -> Option<String>;
20 fn extract_bytes(&self) -> &[u8];
21 fn rewrite_source(&mut self, source: Rc<DOMString>);
22 fn url(&self) -> ServoUrl;
23 fn is_external(&self) -> bool;
24}
25
26pub(crate) fn create_temp_files() -> Option<(NamedTempFile, File)> {
27 let (input, output) = (NamedTempFile::new(), tempfile::tempfile());
33 if let (Ok(input), Ok(output)) = (input, output) {
34 Some((input, output))
35 } else {
36 log::warn!("Error creating input and output temp files");
37 None
38 }
39}
40
41#[derive(Debug)]
42pub(crate) enum BeautifyFileType {
43 Css,
44 Js,
45}
46
47pub(crate) fn execute_js_beautify(input: &Path, output: File, file_type: BeautifyFileType) -> bool {
48 let mut cmd = Command::new("js-beautify");
49 match file_type {
50 BeautifyFileType::Js => (),
51 BeautifyFileType::Css => {
52 cmd.arg("--type").arg("css");
53 },
54 }
55 match cmd.arg(input).stdout(output).status() {
56 Ok(status) => status.success(),
57 _ => {
58 log::warn!(
59 "Failed to execute js-beautify --type {:?}, Will store unmodified script",
60 file_type
61 );
62 false
63 },
64 }
65}
66
67pub(crate) fn create_output_file(
68 unminified_dir: String,
69 url: &ServoUrl,
70 external: Option<bool>,
71) -> Result<File, Error> {
72 let path = PathBuf::from(unminified_dir);
73
74 let (base, has_name) = match url.as_str().ends_with('/') {
75 true => (
76 path.join(&url[url::Position::BeforeHost..])
77 .as_path()
78 .to_owned(),
79 false,
80 ),
81 false => (
82 path.join(&url[url::Position::BeforeHost..])
83 .parent()
84 .unwrap()
85 .to_owned(),
86 true,
87 ),
88 };
89
90 create_dir_all(&base)?;
91
92 let path = if external.unwrap_or(true) && has_name {
93 path.join(&url[url::Position::BeforeHost..])
95 } else {
96 base.join(Uuid::new_v4().to_string())
98 };
99
100 debug!("Unminified files will be stored in {:?}", path);
101
102 File::create(path)
103}
104
105pub(crate) fn unminify_js(script: &mut dyn ScriptSource) {
106 let Some(unminified_dir) = script.unminified_dir() else {
107 return;
108 };
109
110 if let Some((mut input, mut output)) = create_temp_files() {
111 input.write_all(script.extract_bytes()).unwrap();
112
113 if execute_js_beautify(
114 input.path(),
115 output.try_clone().unwrap(),
116 BeautifyFileType::Js,
117 ) {
118 let mut script_content = String::new();
119 output.seek(std::io::SeekFrom::Start(0)).unwrap();
120 output.read_to_string(&mut script_content).unwrap();
121 script.rewrite_source(Rc::new(DOMString::from(script_content)));
122 }
123 }
124
125 match create_output_file(unminified_dir, &script.url(), Some(script.is_external())) {
126 Ok(mut file) => file.write_all(script.extract_bytes()).unwrap(),
127 Err(why) => warn!("Could not store script {:?}", why),
128 }
129}
130
131pub(crate) fn unminified_path(dir: &str) -> String {
132 let mut path = env::current_dir().unwrap();
133 path.push(dir);
134 path.into_os_string().into_string().unwrap()
135}