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::borrow::Cow;
6use std::env;
7use std::fs::{File, create_dir_all};
8use std::io::{Error, ErrorKind, Read, Seek, Write};
9use std::path::{Path, PathBuf};
10use std::process::Command;
11
12use servo_url::ServoUrl;
13use tempfile::NamedTempFile;
14use uuid::Uuid;
15
16pub(crate) struct ScriptSource<'a> {
17    pub source: Cow<'a, str>,
18    pub url: &'a ServoUrl,
19    pub external: bool,
20}
21
22pub(crate) fn create_temp_files() -> Option<(NamedTempFile, File)> {
23    // Write the minified code to a temporary file and pass its path as an argument
24    // to js-beautify to read from. Meanwhile, redirect the process' stdout into
25    // another temporary file and read that into a string. This avoids some hangs
26    // observed on macOS when using direct input/output pipes with very large
27    // unminified content.
28    let (input, output) = (NamedTempFile::new(), tempfile::tempfile());
29    if let (Ok(input), Ok(output)) = (input, output) {
30        Some((input, output))
31    } else {
32        log::warn!("Error creating input and output temp files");
33        None
34    }
35}
36
37#[derive(Debug)]
38pub(crate) enum BeautifyFileType {
39    Css,
40    Js,
41}
42
43pub(crate) fn execute_js_beautify(input: &Path, output: File, file_type: BeautifyFileType) -> bool {
44    let mut cmd = Command::new("js-beautify");
45    match file_type {
46        BeautifyFileType::Js => (),
47        BeautifyFileType::Css => {
48            cmd.arg("--type").arg("css");
49        },
50    }
51    match cmd.arg(input).stdout(output).status() {
52        Ok(status) => status.success(),
53        _ => {
54            log::warn!(
55                "Failed to execute js-beautify --type {:?}, Will store unmodified script",
56                file_type
57            );
58            false
59        },
60    }
61}
62
63pub fn create_output_file(
64    unminified_dir: String,
65    url: &ServoUrl,
66    external: Option<bool>,
67) -> Result<File, Error> {
68    let path = PathBuf::from(unminified_dir);
69
70    if url.scheme() == "data" {
71        return Err(Error::new(
72            ErrorKind::InvalidInput,
73            "data URLs cannot be written as unminified files",
74        ));
75    }
76
77    // Strip the query string from the URL before using it as a file path.
78    // '?' is a reserved character on Windows and causes file creation to fail
79    // silently. BeforeHost..AfterPath stops the slice before the '?' separator.
80    let url_path = &url[url::Position::BeforeHost..url::Position::AfterPath];
81
82    let (base, has_name) = match url.as_str().ends_with('/') {
83        true => (path.join(url_path).as_path().to_owned(), false),
84        false => (path.join(url_path).parent().unwrap().to_owned(), true),
85    };
86
87    create_dir_all(&base)?;
88
89    let path = if external.unwrap_or(true) && has_name {
90        // External.
91        path.join(url_path)
92    } else {
93        // Inline file or url ends with '/'
94        base.join(Uuid::new_v4().to_string())
95    };
96
97    debug!("Unminified files will be stored in {:?}", path);
98
99    File::create(path)
100}
101
102pub(crate) fn unminify_js(script: &mut ScriptSource, unminified_js_dir: String) {
103    if let Some((mut input, mut output)) = create_temp_files() {
104        input.write_all(script.source.as_bytes()).unwrap();
105
106        if execute_js_beautify(
107            input.path(),
108            output.try_clone().unwrap(),
109            BeautifyFileType::Js,
110        ) {
111            let mut script_content = String::new();
112            output.seek(std::io::SeekFrom::Start(0)).unwrap();
113            output.read_to_string(&mut script_content).unwrap();
114            script.source = script_content.into();
115        }
116    }
117
118    match create_output_file(unminified_js_dir, script.url, Some(script.external)) {
119        Ok(mut file) => file.write_all(script.source.as_bytes()).unwrap(),
120        Err(why) => warn!("Could not store script {:?}", why),
121    }
122}
123
124pub(crate) fn unminified_path(dir: &str) -> String {
125    let mut path = env::current_dir().unwrap();
126    path.push(dir);
127    path.into_os_string().into_string().unwrap()
128}