• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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