Skip to main content

script/
unminify.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    // Write the minified code to a temporary file and pass its path as an argument
29    // to js-beautify to read from. Meanwhile, redirect the process' stdout into
30    // another temporary file and read that into a string. This avoids some hangs
31    // observed on macOS when using direct input/output pipes with very large
32    // unminified content.
33    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 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    // Strip the query string from the URL before using it as a file path.
76    // '?' is a reserved character on Windows and causes file creation to fail
77    // silently. BeforeHost..AfterPath stops the slice before the '?' separator.
78    let url_path = &url[url::Position::BeforeHost..url::Position::AfterPath];
79
80    let (base, has_name) = match url.as_str().ends_with('/') {
81        true => (path.join(url_path).as_path().to_owned(), false),
82        false => (path.join(url_path).parent().unwrap().to_owned(), true),
83    };
84
85    create_dir_all(&base)?;
86
87    let path = if external.unwrap_or(true) && has_name {
88        // External.
89        path.join(url_path)
90    } else {
91        // Inline file or url ends with '/'
92        base.join(Uuid::new_v4().to_string())
93    };
94
95    debug!("Unminified files will be stored in {:?}", path);
96
97    File::create(path)
98}
99
100pub(crate) fn unminify_js(script: &mut dyn ScriptSource) {
101    let Some(unminified_dir) = script.unminified_dir() else {
102        return;
103    };
104
105    if let Some((mut input, mut output)) = create_temp_files() {
106        input.write_all(&script.extract_bytes()).unwrap();
107
108        if execute_js_beautify(
109            input.path(),
110            output.try_clone().unwrap(),
111            BeautifyFileType::Js,
112        ) {
113            let mut script_content = String::new();
114            output.seek(std::io::SeekFrom::Start(0)).unwrap();
115            output.read_to_string(&mut script_content).unwrap();
116            script.rewrite_source(Rc::new(DOMString::from(script_content)));
117        }
118    }
119
120    match create_output_file(unminified_dir, &script.url(), Some(script.is_external())) {
121        Ok(mut file) => file.write_all(&script.extract_bytes()).unwrap(),
122        Err(why) => warn!("Could not store script {:?}", why),
123    }
124}
125
126pub(crate) fn unminified_path(dir: &str) -> String {
127    let mut path = env::current_dir().unwrap();
128    path.push(dir);
129    path.into_os_string().into_string().unwrap()
130}