1 // Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "tests/cefclient/browser/preferences_test.h"
6
7 #include <sstream>
8 #include <string>
9 #include <vector>
10
11 #include "include/base/cef_logging.h"
12 #include "include/cef_command_line.h"
13 #include "include/cef_parser.h"
14 #include "tests/cefclient/browser/test_runner.h"
15
16 namespace client {
17 namespace preferences_test {
18
19 namespace {
20
21 const char kTestUrlPath[] = "/preferences";
22
23 // Application-specific error codes.
24 const int kMessageFormatError = 1;
25 const int kPreferenceApplicationError = 1;
26
27 // Common to all messages.
28 const char kNameKey[] = "name";
29 const char kNameValueGet[] = "preferences_get";
30 const char kNameValueSet[] = "preferences_set";
31 const char kNameValueState[] = "preferences_state";
32
33 // Used with "preferences_get" messages.
34 const char kIncludeDefaultsKey[] = "include_defaults";
35
36 // Used with "preferences_set" messages.
37 const char kPreferencesKey[] = "preferences";
38
39 // Handle messages in the browser process. Only accessed on the UI thread.
40 class Handler : public CefMessageRouterBrowserSide::Handler {
41 public:
42 typedef std::vector<std::string> NameVector;
43
Handler()44 Handler() { CEF_REQUIRE_UI_THREAD(); }
45
46 // Called due to cefQuery execution in preferences.html.
OnQuery(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64 query_id,const CefString & request,bool persistent,CefRefPtr<Callback> callback)47 bool OnQuery(CefRefPtr<CefBrowser> browser,
48 CefRefPtr<CefFrame> frame,
49 int64 query_id,
50 const CefString& request,
51 bool persistent,
52 CefRefPtr<Callback> callback) override {
53 CEF_REQUIRE_UI_THREAD();
54
55 // Only handle messages from the test URL.
56 const std::string& url = frame->GetURL();
57 if (!test_runner::IsTestURL(url, kTestUrlPath))
58 return false;
59
60 // Parse |request| as a JSON dictionary.
61 CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
62 if (!request_dict) {
63 callback->Failure(kMessageFormatError, "Incorrect message format");
64 return true;
65 }
66
67 // Verify the "name" key.
68 if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback))
69 return true;
70
71 const std::string& message_name = request_dict->GetString(kNameKey);
72 if (message_name == kNameValueGet) {
73 // JavaScript is requesting a JSON representation of the preferences tree.
74
75 // Verify the "include_defaults" key.
76 if (!VerifyKey(request_dict, kIncludeDefaultsKey, VTYPE_BOOL, callback))
77 return true;
78
79 const bool include_defaults = request_dict->GetBool(kIncludeDefaultsKey);
80
81 OnPreferencesGet(browser, include_defaults, callback);
82
83 return true;
84 } else if (message_name == kNameValueSet) {
85 // JavaScript is requesting that preferences be updated to match the
86 // specified JSON representation.
87
88 // Verify the "preferences" key.
89 if (!VerifyKey(request_dict, kPreferencesKey, VTYPE_DICTIONARY, callback))
90 return true;
91
92 CefRefPtr<CefDictionaryValue> preferences =
93 request_dict->GetDictionary(kPreferencesKey);
94
95 OnPreferencesSet(browser, preferences, callback);
96
97 return true;
98 } else if (message_name == kNameValueState) {
99 // JavaScript is requesting global state information.
100
101 OnPreferencesState(browser, callback);
102
103 return true;
104 }
105
106 return false;
107 }
108
109 private:
110 // Execute |callback| with the preferences dictionary as a JSON string.
OnPreferencesGet(CefRefPtr<CefBrowser> browser,bool include_defaults,CefRefPtr<Callback> callback)111 static void OnPreferencesGet(CefRefPtr<CefBrowser> browser,
112 bool include_defaults,
113 CefRefPtr<Callback> callback) {
114 CefRefPtr<CefRequestContext> context =
115 browser->GetHost()->GetRequestContext();
116
117 // Retrieve all preference values.
118 CefRefPtr<CefDictionaryValue> prefs =
119 context->GetAllPreferences(include_defaults);
120
121 // Serialize the preferences to JSON and return to the JavaScript caller.
122 callback->Success(GetJSON(prefs));
123 }
124
125 // Set preferences based on the contents of |preferences|. Execute |callback|
126 // with a descriptive result message.
OnPreferencesSet(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDictionaryValue> preferences,CefRefPtr<Callback> callback)127 static void OnPreferencesSet(CefRefPtr<CefBrowser> browser,
128 CefRefPtr<CefDictionaryValue> preferences,
129 CefRefPtr<Callback> callback) {
130 CefRefPtr<CefRequestContext> context =
131 browser->GetHost()->GetRequestContext();
132
133 CefRefPtr<CefValue> value = CefValue::Create();
134 value->SetDictionary(preferences);
135
136 std::string error;
137 NameVector changed_names;
138
139 // Apply preferences. This may result in errors.
140 const bool success =
141 ApplyPrefs(context, std::string(), value, error, changed_names);
142
143 // Create a message that accurately represents the result.
144 std::string message;
145 if (!changed_names.empty()) {
146 std::stringstream ss;
147 ss << "Successfully changed " << changed_names.size() << " preferences; ";
148 for (size_t i = 0; i < changed_names.size(); ++i) {
149 ss << changed_names[i];
150 if (i < changed_names.size() - 1)
151 ss << ", ";
152 }
153 message = ss.str();
154 }
155
156 if (!success) {
157 DCHECK(!error.empty());
158 if (!message.empty())
159 message += "\n";
160 message += error;
161 }
162
163 if (changed_names.empty()) {
164 if (!message.empty())
165 message += "\n";
166 message += "No preferences changed.";
167 }
168
169 // Return the message to the JavaScript caller.
170 if (success)
171 callback->Success(message);
172 else
173 callback->Failure(kPreferenceApplicationError, message);
174 }
175
176 // Execute |callback| with the global state dictionary as a JSON string.
OnPreferencesState(CefRefPtr<CefBrowser> browser,CefRefPtr<Callback> callback)177 static void OnPreferencesState(CefRefPtr<CefBrowser> browser,
178 CefRefPtr<Callback> callback) {
179 CefRefPtr<CefCommandLine> command_line =
180 CefCommandLine::GetGlobalCommandLine();
181
182 CefRefPtr<CefDictionaryValue> dict = CefDictionaryValue::Create();
183
184 // If spell checking is disabled via the command-line then it cannot be
185 // enabled via preferences.
186 dict->SetBool("spellcheck_disabled",
187 command_line->HasSwitch("disable-spell-checking"));
188
189 // If proxy settings are configured via the command-line then they cannot
190 // be modified via preferences.
191 dict->SetBool("proxy_configured",
192 command_line->HasSwitch("no-proxy-server") ||
193 command_line->HasSwitch("proxy-auto-detect") ||
194 command_line->HasSwitch("proxy-pac-url") ||
195 command_line->HasSwitch("proxy-server"));
196
197 // If allow running insecure content is enabled via the command-line then it
198 // cannot be enabled via preferences.
199 dict->SetBool("allow_running_insecure_content",
200 command_line->HasSwitch("allow-running-insecure-content"));
201
202 // Serialize the state to JSON and return to the JavaScript caller.
203 callback->Success(GetJSON(dict));
204 }
205
206 // Convert a JSON string to a dictionary value.
ParseJSON(const CefString & string)207 static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
208 CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
209 if (value.get() && value->GetType() == VTYPE_DICTIONARY)
210 return value->GetDictionary();
211 return nullptr;
212 }
213
214 // Convert a dictionary value to a JSON string.
GetJSON(CefRefPtr<CefDictionaryValue> dictionary)215 static CefString GetJSON(CefRefPtr<CefDictionaryValue> dictionary) {
216 CefRefPtr<CefValue> value = CefValue::Create();
217 value->SetDictionary(dictionary);
218 return CefWriteJSON(value, JSON_WRITER_DEFAULT);
219 }
220
221 // Verify that |key| exists in |dictionary| and has type |value_type|. Fails
222 // |callback| and returns false on failure.
VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,const char * key,cef_value_type_t value_type,CefRefPtr<Callback> callback)223 static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
224 const char* key,
225 cef_value_type_t value_type,
226 CefRefPtr<Callback> callback) {
227 if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
228 callback->Failure(
229 kMessageFormatError,
230 "Missing or incorrectly formatted message key: " + std::string(key));
231 return false;
232 }
233 return true;
234 }
235
236 // Apply preferences. Returns true on success. Returns false and sets |error|
237 // to a descriptive error string on failure. |changed_names| is the list of
238 // preferences that were successfully changed.
ApplyPrefs(CefRefPtr<CefRequestContext> context,const std::string & name,CefRefPtr<CefValue> value,std::string & error,NameVector & changed_names)239 static bool ApplyPrefs(CefRefPtr<CefRequestContext> context,
240 const std::string& name,
241 CefRefPtr<CefValue> value,
242 std::string& error,
243 NameVector& changed_names) {
244 if (!name.empty() && context->HasPreference(name)) {
245 // The preference exists. Set the value.
246 return SetPref(context, name, value, error, changed_names);
247 }
248
249 if (value->GetType() == VTYPE_DICTIONARY) {
250 // A dictionary type value that is not an existing preference. Try to set
251 // each of the elements individually.
252 CefRefPtr<CefDictionaryValue> dict = value->GetDictionary();
253
254 CefDictionaryValue::KeyList keys;
255 dict->GetKeys(keys);
256 for (size_t i = 0; i < keys.size(); ++i) {
257 const std::string& key = keys[i];
258 const std::string& current_name = name.empty() ? key : name + "." + key;
259 if (!ApplyPrefs(context, current_name, dict->GetValue(key), error,
260 changed_names)) {
261 return false;
262 }
263 }
264
265 return true;
266 }
267
268 error = "Trying to create an unregistered preference: " + name;
269 return false;
270 }
271
272 // Set a specific preference value. Returns true if the value is set
273 // successfully or has not changed. If the value has changed then |name| will
274 // be added to |changed_names|. Returns false and sets |error| to a
275 // descriptive error string on failure.
SetPref(CefRefPtr<CefRequestContext> context,const std::string & name,CefRefPtr<CefValue> value,std::string & error,NameVector & changed_names)276 static bool SetPref(CefRefPtr<CefRequestContext> context,
277 const std::string& name,
278 CefRefPtr<CefValue> value,
279 std::string& error,
280 NameVector& changed_names) {
281 CefRefPtr<CefValue> existing_value = context->GetPreference(name);
282 DCHECK(existing_value);
283
284 if (value->GetType() == VTYPE_STRING &&
285 existing_value->GetType() != VTYPE_STRING) {
286 // Since |value| is coming from JSON all basic types will be represented
287 // as strings. Convert to the expected data type.
288 const std::string& string_val = value->GetString();
289 switch (existing_value->GetType()) {
290 case VTYPE_BOOL:
291 if (string_val == "true" || string_val == "1")
292 value->SetBool(true);
293 else if (string_val == "false" || string_val == "0")
294 value->SetBool(false);
295 break;
296 case VTYPE_INT:
297 value->SetInt(atoi(string_val.c_str()));
298 break;
299 case VTYPE_DOUBLE:
300 value->SetInt(atof(string_val.c_str()));
301 break;
302 default:
303 // Other types cannot be converted.
304 break;
305 }
306 }
307
308 // Nothing to do if the value hasn't changed.
309 if (existing_value->IsEqual(value))
310 return true;
311
312 // Attempt to set the preference.
313 CefString error_str;
314 if (!context->SetPreference(name, value, error_str)) {
315 error = error_str.ToString() + ": " + name;
316 return false;
317 }
318
319 // The preference was set successfully.
320 changed_names.push_back(name);
321 return true;
322 }
323
324 DISALLOW_COPY_AND_ASSIGN(Handler);
325 };
326
327 } // namespace
328
CreateMessageHandlers(test_runner::MessageHandlerSet & handlers)329 void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
330 handlers.insert(new Handler());
331 }
332
333 } // namespace preferences_test
334 } // namespace client
335