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 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    // Write the minified code to a temporary file and pass its path as an argument
28    // to js-beautify to read from. Meanwhile, redirect the process' stdout into
29    // another temporary file and read that into a string. This avoids some hangs
30    // observed on macOS when using direct input/output pipes with very large
31    // unminified content.
32    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        // External.
94        path.join(&url[url::Position::BeforeHost..])
95    } else {
96        // Inline file or url ends with '/'
97        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}