1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use getopts::{Matches, Options};
use servo::config::opts::{self, ArgumentParsingResult};
use servo::config::prefs::{self, PrefValue};
use servo::embedder_traits;
use servo::servo_config::basedir;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;

pub fn register_user_prefs(opts_matches: &Matches) {
    // Read user's prefs.json and then parse --pref command line args.

    let user_prefs_path = opts::get()
        .config_dir
        .clone()
        .or_else(|| basedir::default_config_dir())
        .map(|path| path.join("prefs.json"))
        .filter(|path| path.exists());

    let mut userprefs = if let Some(path) = user_prefs_path {
        let mut file = File::open(&path).expect("Error opening user prefs");
        let mut txt = String::new();
        file.read_to_string(&mut txt)
            .expect("Can't read user prefs file");
        prefs::read_prefs_map(&txt).expect("Can't parse user prefs file")
    } else {
        HashMap::new()
    };

    let argprefs: HashMap<String, PrefValue> = opts_matches
        .opt_strs("pref")
        .iter()
        .map(|pref| {
            let split: Vec<&str> = pref.splitn(2, '=').collect();
            let pref_name = split[0];
            let pref_value = match split.get(1).cloned() {
                Some("true") | None => PrefValue::Bool(true),
                Some("false") => PrefValue::Bool(false),
                Some(string) => {
                    if let Some(int) = string.parse::<i64>().ok() {
                        PrefValue::Int(int)
                    } else if let Some(float) = string.parse::<f64>().ok() {
                        PrefValue::Float(float)
                    } else {
                        PrefValue::from(string)
                    }
                },
            };
            (pref_name.to_string(), pref_value)
        })
        .collect();

    // --pref overrides user prefs.json
    userprefs.extend(argprefs);

    prefs::add_user_prefs(userprefs);
}

// Use for test
#[allow(dead_code)]
fn test_parse_pref(arg: &str) {
    embedder_traits::resources::set_for_tests();
    let mut opts = Options::new();
    opts.optmulti("", "pref", "", "");
    let args = vec!["servo".to_string(), "--pref".to_string(), arg.to_string()];
    let matches = match opts::from_cmdline_args(opts, &args) {
        ArgumentParsingResult::ContentProcess(m, _) => m,
        ArgumentParsingResult::ChromeProcess(m) => m,
    };
    register_user_prefs(&matches);
}

#[test]
fn test_parse_pref_from_command_line() {
    use servo::servo_config::pref;
    // Test with boolean values.
    test_parse_pref("dom.bluetooth.enabled=true");
    assert_eq!(
        prefs::pref_map().get("dom.bluetooth.enabled"),
        PrefValue::Bool(true)
    );
    assert_eq!(pref!(dom.bluetooth.enabled), true);

    test_parse_pref("dom.bluetooth.enabled=false");
    assert_eq!(
        prefs::pref_map().get("dom.bluetooth.enabled"),
        PrefValue::Bool(false)
    );
    assert_eq!(pref!(dom.bluetooth.enabled), false);

    // Test with numbers
    test_parse_pref("layout.threads=42");
    assert_eq!(pref!(layout.threads), 42);

    // Test string.
    test_parse_pref("shell.homepage=str");
    assert_eq!(pref!(shell.homepage), "str");

    // Test with no value (defaults to true).
    prefs::pref_map()
        .set("dom.bluetooth.enabled", false)
        .unwrap();
    test_parse_pref("dom.bluetooth.enabled");
    assert_eq!(pref!(dom.bluetooth.enabled), true);
}

#[test]
fn test_invalid_prefs_from_command_line_panics() {
    let err_msg = std::panic::catch_unwind(|| {
        test_parse_pref("doesntexist=true");
    })
    .err()
    .and_then(|a| a.downcast_ref::<String>().cloned())
    .expect("Should panic");
    assert!(
        err_msg.starts_with("Error setting preference"),
        "Message should describe the problem"
    );
    assert!(
        err_msg.contains("doesntexist"),
        "Message should mention the name of the preference"
    );
}