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